【问题标题】:The best way to calculate the height in a binary search tree? (balancing an AVL-tree)计算二叉搜索树高度的最佳方法? (平衡 AVL 树)
【发布时间】:2010-10-09 05:07:25
【问题描述】:

我正在寻找在AVL-tree 中计算节点余额的最佳方法。我以为我可以正常工作,但是经过一些繁重的插入/更新后,我可以看到它(根本)无法正常工作。

这是一个两部分的问题,第一部分是如何计算子树的高度,我知道定义“节点的高度是最长向下路径的长度到那个节点的叶子。” 我理解它,但我未能实现它。为了让我更加困惑,这句话可以在 wikipedia on tree-heights 上找到 “通常,值 -1 对应于没有节点的子树,而 0 对应于具有一个节点的子树。”

第二部分是获取AVL树中子树的平衡因子,我理解这个概念没有问题,“获取LR子的高度树并从L 中减去R"。这被定义为:BALANCE = NODE[L][HEIGHT] - NODE[R][HEIGT]

阅读维基百科在描述插入 AVL 树的前几行中说:“如果平衡因子变为 -1、0 或 1,则树仍处于 AVL 形式,不需要旋转。”

然后继续说“如果平衡因子变为2或-2,那么以该节点为根的树是不平衡的,需要树旋转。最多需要单轮或双轮平衡树。” - 我可以毫不费力地掌握。

但是(是的,总有一个但是)。

这就是令人困惑的地方,文本声明 “如果 R 的平衡因子为 1,则表示插入发生在该节点的(外部)右侧,需要左旋转”。但是根据我的理解,文本说(正如我所引用的)如果平衡因子在[-1, 1] 之内,那么就不需要平衡了吗?

我觉得我已经非常接近掌握这个概念了,我已经让树旋转下来,实现了一个正常的二叉搜索树,并且在掌握 AVL-trees 的边缘,但似乎只是缺少那个重要的顿悟。

编辑:代码示例优于学术公式,因为我总是更容易掌握代码中的某些内容,但非常感谢任何帮助。

编辑:我希望我可以将所有答案都标记为“已接受”,但对我来说,尼克的回答是第一个让我“啊哈”的答案。

【问题讨论】:

    标签: algorithm data-structures binary-tree avl-tree tree-balancing


    【解决方案1】:
    • 高度很容易通过递归实现,取子树高度的最大值加一。

    • “R 的平衡因子”是指不平衡的树的右子树。

    【讨论】:

      【解决方案2】:

      第 1 部分 - 高度

      正如 starblue 所说,高度只是递归的。在伪代码中:

      height(node) = max(height(node.L), height(node.R)) + 1
      

      现在可以通过两种方式定义高度。它可以是从根到该节点的路径中的节点数,也可以是链接数。根据page you referenced,最常见的定义是链接数。在这种情况下,完整的伪代码将是:

      height(node): 
         if node == null:
              return -1
         else:
              return max(height(node.L), height(node.R)) + 1
      

      如果你想要节点的数量,代码应该是:

      height(node): 
         if node == null:
              return 0
         else:
              return max(height(node.L), height(node.R)) + 1
      

      不管怎样,我认为再平衡算法应该是一样的。

      但是,如果您在树中存储和更新高度信息,而不是每次都计算它,那么您的树将更加高效 (O(ln(n)))。 (O(n))

      第 2 部分 - 平衡

      当它说“如果R的平衡因子为1”时,它是在谈论右分支的平衡因子,当顶部的平衡因子为2时。它告诉你如何选择是否做一个单转或双转。在(类似python的)伪代码中:

      if balance factor(top) = 2: // right is imbalanced
           if balance factor(R) = 1: // 
                do a left rotation
           else if balance factor(R) = -1:
                do a double rotation
      else: // must be -2, left is imbalanced
           if balance factor(L) = 1: // 
                do a left rotation
           else if balance factor(L) = -1:
                do a double rotation
      

      我希望这是有道理的

      【讨论】:

      • 关于高度,我是否应该坚持“有零个孩子的节点被认为是-1,有一个孩子的节点被认为是0”?
      • 试试 height(node) = max(height(node.L), height(node.R)) + 1;
      • 换句话说,height() 函数在类似 python 的伪代码中看起来如何? (对不起两个cmets,点击添加评论错误)
      • @Josh DOH!干杯。 @fredrikholmstrom - 现在有一些伪代码
      • Nick:en.wikipedia.org/wiki/Tree_height#Height“Terminolog”标题第二段的结尾“按照惯例,值-1对应一个没有节点的子树,而0对应一个有节点的子树一个节点。”
      【解决方案3】:

      嗯,您可以使用以下递归函数计算树的高度:

      int height(struct tree *t) {
          if (t == NULL)
              return 0;
          else
              return max(height(t->left), height(t->right)) + 1;
      }
      

      具有max()struct tree 的适当定义。您应该花时间弄清楚为什么这与您引用的基于路径长度的定义相对应。该函数使用零作为空树的高度。

      但是,对于像 AVL 树这样的东西,我认为您不会在每次需要时实际计算高度。相反,每个树节点都增加了一个额外的字段,该字段记住以该节点为根的子树的高度。当树被插入和删除修改时,该字段必须保持最新。

      我怀疑,如果您每次都计算高度而不是像上面建议的那样将其缓存在树中,那么 AVL 树的形状将是正确的,但它不会具有预期的对数性能。

      【讨论】:

        【解决方案4】:

        这就是令人困惑的地方,文本指出“如果 R 的平衡因子为 1,则意味着插入发生在该节点的(外部)右侧并且需要向左旋转”。但是根据我的理解,文本说(正如我所引用的)如果平衡因子在 [-1, 1] 范围内,那么就不需要平衡?

        R 是当前节点N 的右手子节点。

        如果是balance(N) = +2,那么您需要进行某种轮换。但是使用哪个轮换?好吧,这取决于balance(R):如果balance(R) = +1,那么您需要在N 上左旋;但如果是balance(R) = -1,那么您将需要某种双重旋转。

        【讨论】:

          【解决方案5】:

          您不需要即时计算树的深度。

          您可以在执行操作时维护它们。

          此外,您实际上不必跟踪深度;您可以简单地跟踪左右树深度之间的差异。

          http://www.eternallyconfuzzled.com/tuts/datastructures/jsw_tut_avl.aspx

          我发现从编程 POV 中更容易跟踪平衡因子(左右子树之间的差异),除了在旋转后整理平衡因子是 PITA...

          【讨论】:

            【解决方案6】:

            这就是令人困惑的地方,文本指出“如果 R 的平衡因子是 1,它 表示插入发生在该节点的(外部)右侧和左侧 需要轮换”。但根据我对文本的理解(正如我所引用的),如果 平衡因子在 [-1, 1] 范围内,那么不需要平衡吗?

            好的,顿悟时间。

            考虑一下旋转的作用。让我们考虑一下左旋转。

             P = parent
             O = ourself (the element we're rotating)
             RC = right child
             LC = left child (of the right child, not of ourself)
            
             P              10
              \               \
               O               15
                \                \
                 RC               20
                /                /
               LC               18
            
                      ↓
             P              10
               \               \
                 RC              20
               /               /
              O              15
               \               \
               LC              18
            
             basically, what happens is;
            
             1. our right child moves into our position
             2. we become the left child of our right child
             3. our right child's left child becomes our right
            

            现在,您必须在此处注意一件大事 - 左旋转并没有改变树的深度。我们已经不再平衡了。

            但是 - 这就是 AVL 的魔力 - 如果我们将正确的孩子旋转到正确的 FIRST,我们将拥有的是......

             P
              \
               O
                \
                 LC
                  \
                   RC
            

            现在如果我们将 O 向左旋转,我们得到的是这个......

             P
               \
                 LC
                /  \
               O   RC
            

            魔法!我们已经设法摆脱了树的一个级别 - 我们已经使树平衡了

            平衡树意味着摆脱多余的深度,并更完整地打包上层 - 这正是我们刚刚所做的。

            关于单/双旋转的全部内容就是你必须让你的子树看起来像这样;

             P
              \
               O
                \
                 LC
                  \
                   RC
            

            在您旋转之前 - 您可能需要进行正确的旋转才能进入该状态。但是如果你已经处于那个状态,你只需要左旋转。

            【讨论】:

              【解决方案7】:

              这是另一种计算高度的方法。向您的节点添加一个名为 height 的附加属性:

              class Node
              {
              data value; //data is a custom data type
              node right;
              node left;
              int height;
              }
              

              现在,我们将对树进行简单的广度优先遍历,并不断更新每个节点的高度值:

              int height (Node root)
              {
              Queue<Node> q = Queue<Node>();
              Node lastnode;
              //reset height
              root.height = 0;
              
              q.Enqueue(root);
              while(q.Count > 0)
              {
                 lastnode = q.Dequeue();
                 if (lastnode.left != null){
                    lastnode.left.height = lastnode.height + 1; 
                    q.Enqueue(lastnode.left);
                 }
              
                 if (lastnode.right != null){
                    lastnode.right.height = lastnode.height + 1;
                    q.Enqueue(lastnode.right);
                 }
              }
              return lastnode.height; //this will return a 0-based height, so just a root has a height of 0
              }
              

              干杯,

              【讨论】:

                【解决方案8】:

                BinaryTree&lt;T, Comparator&gt;::Node一个subtreeHeight数据成员,在它的构造函数中初始化为0,并且每次都自动更新:

                template <typename T, typename Comparator>
                inline void BinaryTree<T, Comparator>::Node::setLeft (std::shared_ptr<Node>& node) {
                    const std::size_t formerLeftSubtreeSize = left ? left->subtreeSize : 0;
                    left = node;
                    if (node) {
                        node->parent = this->shared_from_this();
                        subtreeSize++;
                        node->depthFromRoot = depthFromRoot + 1;
                        const std::size_t h = node->subtreeHeight;
                        if (right)
                            subtreeHeight = std::max (right->subtreeHeight, h) + 1;
                        else
                            subtreeHeight = h + 1;
                    }
                    else {
                        subtreeSize -= formerLeftSubtreeSize;
                        subtreeHeight = right ? right->subtreeHeight + 1 : 0;
                    }
                }
                
                template <typename T, typename Comparator>
                inline void BinaryTree<T, Comparator>::Node::setRight (std::shared_ptr<Node>& node) {
                    const std::size_t formerRightSubtreeSize = right ? right->subtreeSize : 0;
                    right = node;
                    if (node) {
                        node->parent = this->shared_from_this();
                        subtreeSize++;
                        node->depthFromRoot = depthFromRoot + 1;
                        const std::size_t h = node->subtreeHeight;
                        if (left)
                            subtreeHeight = std::max (left->subtreeHeight, h) + 1;
                        else
                            subtreeHeight = h + 1;
                    }
                    else {
                        subtreeSize -= formerRightSubtreeSize;
                        subtreeHeight = left ? left->subtreeHeight + 1 : 0;
                    }
                }
                

                请注意,数据成员 subtreeSizedepthFromRoot 也会更新。 插入节点时会调用这些函数(所有已测试),例如

                template <typename T, typename Comparator>
                inline std::shared_ptr<typename BinaryTree<T, Comparator>::Node>
                BinaryTree<T, Comparator>::Node::insert (BinaryTree& tree, const T& t, std::shared_ptr<Node>& node) {
                    if (!node) {
                        std::shared_ptr<Node> newNode = std::make_shared<Node>(tree, t);
                        node = newNode;
                        return newNode;
                    }
                    if (getComparator()(t, node->value)) {
                        std::shared_ptr<Node> newLeft = insert(tree, t, node->left);
                        node->setLeft(newLeft);
                    }
                    else {
                        std::shared_ptr<Node> newRight = insert(tree, t, node->right);
                        node->setRight(newRight);
                    }
                    return node;
                }
                

                如果删除节点,请使用不同版本的removeLeftremoveRight,将subtreeSize++; 替换为subtreeSize--;rotateLeftrotateRight 的算法也可以毫无问题地进行调整。以下测试并通过:

                template <typename T, typename Comparator>
                void BinaryTree<T, Comparator>::rotateLeft (std::shared_ptr<Node>& node) {  // The root of the rotation is 'node', and its right child is the pivot of the rotation.  The pivot will rotate counter-clockwise and become the new parent of 'node'.
                    std::shared_ptr<Node> pivot = node->right;
                    pivot->subtreeSize = node->subtreeSize;
                    pivot->depthFromRoot--;
                    node->subtreeSize--;  // Since 'pivot' will no longer be in the subtree rooted at 'node'.
                    const std::size_t a = pivot->left ? pivot->left->subtreeHeight + 1 : 0;  // Need to establish node->heightOfSubtree before pivot->heightOfSubtree is established, since pivot->heightOfSubtree depends on it.
                    node->subtreeHeight = node->left ? std::max(a, node->left->subtreeHeight + 1) : std::max<std::size_t>(a,1);
                    if (pivot->right) {
                        node->subtreeSize -= pivot->right->subtreeSize;  // The subtree rooted at 'node' loses the subtree rooted at pivot->right.
                        pivot->subtreeHeight = std::max (pivot->right->subtreeHeight, node->subtreeHeight) + 1;
                    }
                    else
                        pivot->subtreeHeight = node->subtreeHeight + 1;
                    node->depthFromRoot++;
                    decreaseDepthFromRoot(pivot->right);  // Recursive call for the entire subtree rooted at pivot->right.
                    increaseDepthFromRoot(node->left);  // Recursive call for the entire subtree rooted at node->left.
                    pivot->parent = node->parent;
                    if (pivot->parent) {  // pivot's new parent will be its former grandparent, which is not nullptr, so the grandparent must be updated with a new left or right child (depending on whether 'node' was its left or right child).
                        if (pivot->parent->left == node)
                            pivot->parent->left = pivot;
                        else
                            pivot->parent->right = pivot;
                    }
                    node->setRightSimple(pivot->left);  // Since pivot->left->value is less than pivot->value but greater than node->value.  We use the NoSizeAdjustment version because the 'subtreeSize' values of 'node' and 'pivot' are correct already.
                    pivot->setLeftSimple(node);
                    if (node == root) {
                        root = pivot;
                        root->parent = nullptr; 
                    }
                }
                

                在哪里

                inline void decreaseDepthFromRoot (std::shared_ptr<Node>& node) {adjustDepthFromRoot(node, -1);}
                inline void increaseDepthFromRoot (std::shared_ptr<Node>& node) {adjustDepthFromRoot(node, 1);}
                
                template <typename T, typename Comparator>
                inline void BinaryTree<T, Comparator>::adjustDepthFromRoot (std::shared_ptr<Node>& node, int adjustment) {
                    if (!node)
                        return;
                    node->depthFromRoot += adjustment;
                    adjustDepthFromRoot (node->left, adjustment);
                    adjustDepthFromRoot (node->right, adjustment);
                }
                

                这是完整的代码:http://ideone.com/d6arrv

                【讨论】:

                  【解决方案9】:

                  这种类似 BFS 的解决方案非常简单。只需一层一层地跳跃。

                  def getHeight(self,root, method='links'):
                      c_node = root
                      cur_lvl_nodes = [root]
                      nxt_lvl_nodes = []
                      height = {'links': -1, 'nodes': 0}[method]
                  
                      while(cur_lvl_nodes or nxt_lvl_nodes):
                          for c_node in cur_lvl_nodes:
                              for n_node in filter(lambda x: x is not None, [c_node.left, c_node.right]):
                                  nxt_lvl_nodes.append(n_node)
                  
                          cur_lvl_nodes = nxt_lvl_nodes
                          nxt_lvl_nodes = []
                          height += 1
                  
                      return height
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2010-09-13
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2013-12-18
                    相关资源
                    最近更新 更多