平衡AVL树haskell

时间:2013-12-22 21:18:14

标签: haskell avl-tree

我在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

1 个答案:

答案 0 :(得分:9)

实现仍然缺少很多东西,你应该多读一些所需内容(甚至维基百科有一个很好的描述,但是如果你进行搜索会出现很多其他页面。)

编写任何(自平衡)树的最棘手的部分是在平衡代码中....只是在Haskell中创建一个树很容易,就像你上面所做的那样,但保持搜索log(N)有点更难。对于AVL,您将需要添加更多代码来执行以下操作 -

  1. 为测量该节点高度的每个节点添加一个int。高度测量为距离最远叶片的距离。 AVL的工作原理是验证左右子节点的高度之间的差异是否相差不超过1.重要的是要注意,这个高度需要直接添加到节点而不是在需要时计算(如某些演示代码一样)互联网确实如此),否则即使是简单的插入也需要遍历整个树而不再是log N.

  2. 添加代码以便在插入后走向节点树(可能最好的位置是插入本身,因为您已经将树向下走到插入点,您可以放置​​检查递归后)。计算高度(只需将下面两个高度的最大值加1,您刚刚在递归调用中计算),并检查平衡因子(height of right - height of left)。如果事情不平衡(即 - |balance factor| > 1),你必须......

  3. 调用旋转功能。旋转是类型为AVLTree->AVLTree的操作。直观地,它将树中的顶部值推到左侧(或根据不平衡的方向向右),从而恢复平衡。右侧节点成为新的顶级节点,并将其左侧节点移至旧的顶部,然后成为新的左侧节点。这是视觉 -

  4.    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填充,所以很多情况都没有了。您将希望用变量填充大部分变量,并将它们移动到右侧的适当位置。