【问题标题】:Red-black tree - How to find the node's parent?红黑树 - 如何找到节点的父节点?
【发布时间】:2011-03-21 19:03:20
【问题描述】:

在红黑树中,当旋转时,你需要知道谁是特定节点的父节点。 但是,该节点仅具有对右子或左子的引用。

我想给一个节点实例变量“父”,但正因为这个原因,我认为这样做不值得,而且每次旋转更改父引用也太复杂了。

public class Node {
  private left;
  private right;
  private isRed;
  private parent; //I don't think this is good idea
}

所以,我的解决方案是编写 findParent() 方法,使用搜索来查找父级。我想知道是否有其他方法可以找到节点的父节点?

我的解决方案:

样本树:

    50
    / \
   25  75

如果你想找到节点 25 的父节点,你可以传递如下内容:

Node parent = findParent(Node25.value);

它返回node50。

protected Node findParent(int val) {
        if(root == null) {
            return null;
        }
        Node current = root;
        Node parent = current;
        while(true) {
            if(current.val == val) { //found
                return parent;
            }
            if(current == null) { //not found
                return null;
            }
            parent = current;
            if(current.val > val) { //go left
                current = current.left;
            }
            else { //go right
                current = current.right; 
            }
        }
    }

【问题讨论】:

  • 我可能错了,但我认为通常的做法是将父级作为调用堆栈上的参数传递给旋转函数。
  • 你应该看看 libavl。正确的方法是在搜索时将祖先保留在堆栈中。

标签: java parent red-black-tree


【解决方案1】:

存储父级肯定比查找它更好。更新父引用并不复杂。

【讨论】:

    【解决方案2】:

    我想给节点实例变量“父”,但正因为如此,我认为不值得这样做

    让您的节点有一个parent 引用需要每个节点一个额外的指针/引用。将此与在需要知道给定节点的父节点时需要遍历树进行比较。

    这是一个权衡

    1. 维护额外引用并在修改节点时使其保持最新的成本。
    2. 必须遍历树才能找到给定节点的父节点的计算成本和复杂性

    我认为这两个选项之间的选择有些主观,但我个人会选择简单地跟踪 parent 引用。

    作为您的参考点,java.util.TreeMap 实现为红黑树,其中 Entry 节点包含 left、right 和 parent 引用。

    【讨论】:

    • 我不认为这是主观的。如果您打算完全拥有一棵 R-B 树,那可能是因为您愿意牺牲一些东西来追求更快的操作。如果您对内存非常敏感,为什么不直接使用数组呢?
    • 嗯,我认为有一些算法可以说,不必维护数据结构的权衡对于计算复杂性的小幅增加是值得的。我个人认为这不是其中之一。我只是想避免给出一个宽泛的答案。
    【解决方案3】:

    当您遍历树以到达您的枢轴节点时,您可以缓存前一个父节点,或者如果您需要多个级别的“撤消”,您可以将每个遍历的节点缓存到堆栈中。

    此缓存将是您的旋转算法的本地变量,因此它不需要树中的更多空间或昂贵的额外遍历。

    【讨论】:

      【解决方案4】:

      父指针的使用是可选的。如果您放弃父指针,那么您将不得不使用递归编写插入/删除操作(递归方法调用将父信息保存在堆栈上)或编写一个迭代版本,该版本在向下移动时维护自己的父堆栈。

      可以在这里找到对红黑树的非常好的描述

      http://adtinfo.org/

      这包括对许多 rbtree 实现的描述,包括有和没有父指针。

      如果您确实想节省空间(这很公平),可以在此处找到对 rbtree 实现的非常出色的描述

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

      如果插入/删除实现使用您所描述的用于搜索节点父级的方法,效率将非常低。使用指针或使用递归。

      【讨论】:

      【解决方案5】:

      另一个解决方案,除了父指针和再次查询父节点之外,是维护一个祖先堆栈。

      假设有人希望将 23 插入以下树中:

      Red Black Tree

      一般要插入的算法是:

      1. 如果它在树中,则查找 23 所在的节点

      2. 如果 23 已经存在,则返回失败

      3. 如果 23 还没有,就把它放在那里。

      4. 根据需要运行重新平衡/着色例程。

      现在,要使用堆栈方法,您分配一个足够大的堆栈以支持树的每一层都有一个节点(我认为 2 * Ceiling(Log2(count)) + 2)应该可以满足您的要求。您甚至可以保留为插入或删除分配的堆栈,并在开始插入时将其清除。

      所以——看根。将其推入堆栈。 23 大于根中的值,所以向右走。现在将节点当前节点(值 21)推入堆栈。如果 23 在树中,它必须在当前节点的右侧。但是当前节点右侧的节点是一个空哨兵。因此,该 null-sentinel 应替换为具有您的值的节点。父项是堆栈顶部的项目(最近被推送),祖父母是下一个......等等。因为你似乎正在学习...... Java为你提供了一个堆栈接口,所以你不需要开发自己的堆栈来做到这一点。就用他们的吧。

      至于这是否比父指针方法更好,这对我来说似乎是有争议的——为了简单和消除维护辅助数据结构或广泛使用递归的需要,我倾向于使用父指针方法。也就是说,任何一种方法都比在应用重新平衡/着色例程时查询当前节点的父节点更好。

      【讨论】:

        猜你喜欢
        • 2014-01-13
        • 1970-01-01
        • 2011-09-18
        • 2015-11-17
        • 2017-03-02
        • 1970-01-01
        • 2011-04-23
        • 2014-01-15
        • 2020-10-03
        相关资源
        最近更新 更多