我在Haskell中创建了一个AVL树,但我不确定如何平衡树。我可以添加元素,但它们不平衡。比如使用我在[4,2,1,3,6,8]中添加的addList方法将其添加为:
布局:根4(根2(根1空空)(根2空空))(根6空(根8空空))
应打印为:
4
2 6
1 3 8
我想平衡一棵树,但不知道如何正确实现它,这是我的代码到目前为止,任何有关这方面的帮助都会很棒。
data AVLTree a = Empty | Root a (AVLTree a) (AVLTree a)
deriving (Eq, Ord, Show)
leaf a = Root a Empty Empty
addNode :: Integral a => a -> AVLTree a -> AVLTree a
addNode a Empty = leaf a
addNode x (Root a left right)
| x > a = Root a left (addNode x right)
| x < a = Root a (addNode x left) right
| otherwise = (Root a left right)
addList :: (Integral a) => [a] -> AVLTree a
addList [] = Empty
addList [n] = leaf n
addList (x:xs) = addNode x (addList xs)
search :: Integral a => AVLTree a -> a -> Bool
search Empty _ = False
search (Root a left right) x
| x == a = True
| x < a = search left x
| x > a = search right x
-- balance :: AVLTree a -> AVLTree a
-- balance Empty = Empty
-- balance tree
-- |(Root r (Root left (Root left1 Empty Empty) Empty) Empty) = (Root left left1 r) -- left left case
-- |(Root r Empty (Root right Empty Empty) (Root right Empty Empty)) = (Root right r right1) -- right right case
-- |(Root r (Root left Empty (Root right Empty Empty)) Empty) = (Root r (Root right (Root left Empty Empty) Empty) Empty) -- left right case
-- |(Root r Empty (Root right (Root left Empty Empty) Empty)) = (Root r Empty (Root left Empty (Root right Empty Empty))) -- right left case
答案 0 :(得分:9)
实现仍然缺少很多东西,你应该多读一些所需内容(甚至维基百科有一个很好的描述,但是如果你进行搜索会出现很多其他页面。)
编写任何(自平衡)树的最棘手的部分是在平衡代码中....只是在Haskell中创建一个树很容易,就像你上面所做的那样,但保持搜索log(N)有点更难。对于AVL,您将需要添加更多代码来执行以下操作 -
为测量该节点高度的每个节点添加一个int。高度测量为距离最远叶片的距离。 AVL的工作原理是验证左右子节点的高度之间的差异是否相差不超过1.重要的是要注意,这个高度需要直接添加到节点而不是在需要时计算(如某些演示代码一样)互联网确实如此),否则即使是简单的插入也需要遍历整个树而不再是log N.
添加代码以便在插入后走向节点树(可能最好的位置是插入本身,因为您已经将树向下走到插入点,您可以放置检查递归后)。计算高度(只需将下面两个高度的最大值加1,您刚刚在递归调用中计算),并检查平衡因子(height of right - height of left
)。如果事情不平衡(即 - |balance factor| > 1
),你必须......
调用旋转功能。旋转是类型为AVLTree->AVLTree
的操作。直观地,它将树中的顶部值推到左侧(或根据不平衡的方向向右),从而恢复平衡。右侧节点成为新的顶级节点,并将其左侧节点移至旧的顶部,然后成为新的左侧节点。这是视觉 -
a c / \ / \ b c -> a e / \ / \ d e b d
请注意,每次不平衡时需要旋转一次或两次,具体取决于您要进行的简单检查....如果顶部是未压缩的(例如,向右),则在将顶部旋转到顶部之前如果右边的节点左边有任何不平衡,你可能需要向右旋转右边的节点(即使是1,不像顶部,可以有-1,0或1而且不需要旋转)。旋转代码是相同的,因此您应该使用相同的功能进行顶部和右侧旋转(尽管您可能希望使用单独的版本将旋转到右侧或左侧)。
如果你为每个插入运行这个平衡代码,那就足够了....如果你曾经添加节点而不这样做,你可能需要在树之前在所有节点上重复这个过程几次平衡,这将需要更多的log(N)计算....
正如我写的那样,我注意到我几乎没有写任何Haskell特定的信息....这个信息适用于任何语言。我认为应该如此,没有理由认为实现应该因为我们在Haskell中而有所不同。如果您在映射到Haskell时遇到任何问题,如何完成其中的一些操作,请在评论中提问,我也可以填写。
另请注意,如果您只想要一个具有Log(N)插入/删除的对象,Haskell已经具有Data.Sequence类型。如果这对你有用,你就不必自己写任何一个。
编辑以发表评论 -
你提到的高度函数有我上面提到的问题 - 你在整个树中递归以找到任何节点的高度。插入将不再具有log N复杂性,这取决于首先拥有树的全部要点。 (据我所知,Haskell并没有记住任何这些值......如果有人不知道,请随意插入,这将简化此代码。)
再详细说明一下,您需要像这样扩展AVLTree的定义 -
data AVLTree a = Empty | Node a (AVLTree a) (AVLTree a) Int
现在高度就是这样 -
height (AVLTree _ _ _ height) = height
然后你的balanceFactor工作 -
balanceFactor :: AVLTree a -> Int
balanceFactor Empty = 0
balanceFactor (Root a left right) = (height right) - (height left)
缺点是你必须添加代码,以便在更改后重新计算整个链中的高度到根。
然后,您需要编写另一个函数来检查是否需要轮换,并在需要时应用它 -
rotateIfNeeded::AVLTree->AVLTree
rotateIfNeeded tree
| b > 1 = fullRotateLeft tree
| b < -1 = fullRotateRight tree
| otherwise = tree
where b = balanceFactor tree
的功能fullRotateLeft / fullRotateRight周围rotateLeft / rotateRight,它首先检查右/左树木需要反向旋转,然后再应用所有需要的旋转。
一个包装你的rotateRight / rotateLeft函数不起作用....左边的太多变量用Empty填充,所以很多情况都没有了。您将希望用变量填充大部分变量,并将它们移动到右侧的适当位置。