在红黑树中,旋转时,您需要知道谁是特定节点的父节点。 但是,该节点仅引用右子或左子。
我正在考虑给出一个节点实例变量“parent”,但正因如此,我认为不值得这样做,而且每次轮换更改父引用也太复杂了。
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;
}
}
}
答案 0 :(得分:3)
我正在考虑给一个节点实例变量“parent”但是因为这个原因我不认为这样做是值得的
让节点具有parent
引用需要每个节点一个额外的指针/引用。将此与需要在需要知道给定节点的父节点时遍历树进行比较。
这是
之间的权衡我认为这两种选择之间的选择有点主观,但我个人会选择简单地跟踪parent
引用。
作为您的参考点,java.util.TreeMap
被实现为红黑树,其中Entry
个节点包含left
,right
和{{1}参考。
答案 1 :(得分:3)
当您遍历树以到达您的枢轴节点时,您可以缓存前一个父节点,或者如果您需要多个“撤消”级别,则可以将每个遍历节点缓存到堆栈。
此缓存是旋转算法的本地变量,因此不需要树中的任何空间或昂贵的额外遍历。
答案 2 :(得分:2)
存储父母比查找它更好。更新父引用并不复杂。
答案 3 :(得分:0)
使用父指针是可选的。如果放弃父指针,则必须使用递归编写插入/删除操作(递归方法调用保留堆栈上的父信息)或编写一个迭代版本,在向下移动树时保持自己的父堆栈。
可以在这里找到关于红黑树的非常好的描述
其中包括许多rbtree实现的描述,包括有和没有父指针。
如果你确实想节省空间(这是公平的),可以在这里找到rbtree实现的非常好的描述
http://www.eternallyconfuzzled.com/tuts/datastructures/jsw_tut_rbtree.aspx
如果插入/删除实现使用了您搜索节点父节点所描述的方法效率非常低。使用指针或使用递归。
答案 4 :(得分:0)
另一个解决方案,除了父指针和重新查询父级外,还要维护一个祖先堆栈。
假设某人希望将23插入到以下树中:
通常,要插入的算法是:
查找节点,如果它在树中,则为23
如果已经存在23,则返回失败
如果23还没有,请把它放在那里。
根据需要运行您的重新平衡/着色程序。
现在,要使用堆栈方法,您需要分配一个足够大的堆栈,以支持每个树级别的一个节点(我认为2 * Ceiling(Log2(count))+ 2)应该覆盖您。你甚至可以保留一个分配用于插入或删除的堆栈,只要你开始插入就清除它。
所以 - 看看根。将它推到堆栈上。 23大于根中的值,所以右转。现在将节点当前节点(值21)推入堆栈。如果23位于树中,则它必须位于当前节点的右侧。但是当前节点右侧的节点是空哨兵。因此,应该用具有您的值的节点替换该null-sentinel。父亲是堆栈顶部的项目(最近被推送),祖父母是下一个...等等。因为你似乎在学习... Java为你提供了一个堆栈界面,所以你赢了'我需要开发自己的堆栈才能做到这一点。只需使用他们的。
至于这是否比父指针方法更好,这对我来说似乎有争议 - 我将倾向于父指针方法以简化并消除维护辅助数据结构或广泛使用递归的需要。也就是说,在应用重新平衡/着色例程时,任何一种方法都比查询当前节点的父节点更好。