使用红黑树的字典 - 删除错误
Posted
技术标签:
【中文标题】使用红黑树的字典 - 删除错误【英文标题】:Dictionary using Red-Black tree - deletion error 【发布时间】:2017-01-12 06:54:54 【问题描述】:我正在尝试使用红黑树实现字典。 我已经测试了插入方法,它似乎工作得很好,RBtree 似乎保持了正确的形状和颜色。执行二叉树节点删除的方法似乎是正确的,但我在删除结束时调用的 deleteFixUp 方法上有很大的问题。
你愿意帮我弄清楚我做错了什么吗?当然,如果您有任何改进我的代码的建议,我们将不胜感激。
RBTreeWParentDictionary.java(这里我实现了 RedBlackTree)
package dictionary;
import java.util.Comparator;
public class RBTreeWParentDictionary<K, V> implements IDictionary<K, V>
/**
* The root node of the RBTreeWParentDictionary
*/
public RBTreeWParentNode<K, V> root;
/**
* Object used to compare two T objects.
*/
private Comparator<K> comparator;
private int length;
/**
* Creates the dictionary based on red/black tree with null root
*
* @param comparator
* The comparator for keys
*/
public RBTreeWParentDictionary(Comparator<K> comparator)
this.root = null;
this.comparator = comparator;
this.length = 0;
/**
* Checks if the tree is empty
*
* @return True if the tree is empty
*/
public boolean isEmpty()
return this.root == null;
/**
* Returns the number of elements in the tree
*
* @return The number of elements in the tree
*/
public int length()
return this.length;
/**
* Performs a left rotation on the tree node
*
* @param node
* The node on which rotate
*/
private void rotateLeft(RBTreeWParentNode<K, V> node)
RBTreeWParentNode<K, V> y = node.getRight();
node.setRight(y.getLeft());
if (y.hasLeft())
y.getLeft().setParent(node);
y.setParent(node.getParent());
if (!node.hasParent()) // = this.isEmpty()
this.root = y;
else
if (node.equals(node.getParent().getLeft()))
node.getParent().setLeft(y);
else
node.getParent().setRight(y);
y.setLeft(node);
/**
* Performs a right rotation on the tree node
*
* @param node
* The node on which rotate
*/
private void rotateRight(RBTreeWParentNode<K, V> node)
RBTreeWParentNode<K, V> y = node.getLeft();
node.setLeft(y.getRight());
if (y.hasRight())
y.getRight().setParent(node);
y.setParent(node.getParent());
if (!node.hasParent())
this.root = y;
else
if (node.equals(node.getParent().getRight()))
node.getParent().setRight(y);
else
node.getParent().setLeft(y);
y.setRight(node);
/*
* Uses for first tests, now removed
*
* public void testRotateLeft() this.rotateLeft(this.root);
*
* public void testRotateRight() this.rotateRight(this.root);
*/
/**
* Performs all the needed work to the tree under the 3 main rules of R/BTree
*
* @param node
* The current node that needs to be checked
*/
private void treeFixUp(RBTreeWParentNode<K, V> node)
RBTreeWParentNode<K, V> u;
if (!node.hasParent())
return;
while (node.getParent().isRed())
if (node.getParent().equals(node.getParent().getParent().getLeft()))
u = node.getParent().getParent().getRight();
if (u != null && u.isRed())
node.getParent().setBlack();
u.setBlack();
node.getParent().getParent().setRed();
node = node.getParent().getParent();
else
if (node.equals(node.getParent().getRight()))
node = node.getParent();
rotateLeft(node);
node.getParent().setBlack();
node.getParent().getParent().setRed();
rotateRight(node.getParent().getParent());
else
u = node.getParent().getParent().getLeft();
if (u != null && u.isRed())
node.getParent().setBlack();
u.setBlack();
node.getParent().getParent().setRed();
node = node.getParent().getParent();
else
if (node.equals(node.getParent().getLeft()))
node = node.getParent();
rotateRight(node);
node.getParent().setBlack();
node.getParent().getParent().setRed();
rotateLeft(node.getParent().getParent());
if (!node.hasParent())
node.setBlack();
break;
/**
* Inserts a node with give key/value
*
* @param key
* The key of the node to be inserted
* @param value
* The value of the node to be inserted
*/
@Override
public void insert(K key, V value)
int res;
RBTreeWParentNode<K, V> insertedNode = new RBTreeWParentNode<K, V>(key,
value);
if (this.isEmpty())
this.root = insertedNode;
this.root.setBlack();
else
RBTreeWParentNode<K, V> node = this.root;
while (node != null)
res = comparator.compare(key, node.getKey());
if (res < 0)
if (node.hasLeft())
node = node.getLeft();
else break;
else if (res > 0)
if (node.hasRight())
node = node.getRight();
else break;
else // duplicate key, overwriting
node.setValue(value);
return;
res = comparator.compare(key, node.getKey());
if (res < 0)
node.setLeft(insertedNode);
else
node.setRight(insertedNode);
treeFixUp(insertedNode);
this.length++;
@Override
public V get(K key)
// TODO Auto-generated method stub
return null;
@Override
public void delete(K key)
RBTreeWParentNode<K, V> node = root;
boolean oldColor;
int res;
while (node != null
&& (res = comparator.compare(key, node.getKey())) != 0)
if (res < 0) node = node.getLeft();
else node = node.getRight();
if (node == null)
return;
oldColor = node.getColor();
// key found, work with children
if (!node.hasParent()) //In root
root = null;
return;
else if(node.hasLeft() && !node.hasRight()) //left child
node.getLeft().setParent(node.getParent());
node.getParent().setLeft(node.getLeft());
else if (!node.hasLeft() && node.hasRight()) //right child
node.getRight().setParent(node.getParent());
node.getParent().setRight(node.getRight());
else if (node.hasLeft() && node.hasRight()) //both children
RBTreeWParentNode<K, V> tmp = node;
node = min(tmp.getRight());
//fix parent node of node
node.setParent(tmp.getParent());
if (tmp.getParent().getLeft().equals(tmp))
node.getParent().setLeft(node);
else node.getParent().setRight(node);
node.setRight(deleteMin(tmp.getRight()));
node.setLeft(tmp.getLeft());
tmp = null;
else // is a leaf
if (node.equals(node.getParent().getLeft()) )
node.getParent().setLeft(null);
else node.getParent().setRight(null);
if (oldColor == false)
deleteFixUp(node);
private RBTreeWParentNode<K, V> deleteMin(
RBTreeWParentNode<K, V> node)
if (node.getLeft() == null)
return node.getRight();
node.setLeft(deleteMin(node.getLeft()));
return node;
private RBTreeWParentNode<K, V> min(RBTreeWParentNode<K, V> node)
if (node.getLeft() == null)
return node;
else return min(node.getLeft());
private void deleteFixUp(RBTreeWParentNode<K, V> node)
while (!node.equals(this.root) && node.isBlack())
if (node.equals(node.getParent().getLeft()))
if (node.getParent().hasRight())
RBTreeWParentNode<K, V> w = node.getParent().getRight();
if (w.isRed())
w.setBlack();
node.getParent().setRed();
rotateLeft(node.getParent());
w=node.getParent().getRight();
if (w.hasLeft() && w.hasRight() && w.getLeft().isBlack() && w.getRight().isBlack())
w.setRed();
node = node.getParent();
else
if (w.hasRight() && w.getRight().isBlack())
w.getLeft().setBlack();
w.setRed();
rotateRight(w);
w = node.getParent().getRight();
w.setColor(node.getParent().getColor());
node.getParent().setBlack();
w.getRight().setBlack();
rotateLeft(node.getParent());
node = this.root;
else
//Repeat up changing left with right
if (node.getParent().hasLeft())
RBTreeWParentNode<K, V> w = node.getParent().getLeft();
if (w.isRed())
w.setBlack();
node.getParent().setRed();
rotateRight(node.getParent());
w=node.getParent().getLeft();
if (w.hasLeft() && w.hasRight() && w.getLeft().isBlack() && w.getRight().isBlack())
w.setRed();
node = node.getParent();
else
if (w.hasLeft() && w.getLeft().isBlack())
w.getRight().setBlack();
w.setRed();
rotateLeft(w);
w = node.getParent().getLeft();
w.setColor(node.getParent().getColor());
node.getParent().setBlack();
w.getLeft().setBlack();
rotateRight(node.getParent());
node = this.root;
node.setBlack();
@SuppressWarnings("unused")
@Override
public boolean equals(Object other)
if (!(other instanceof RBTreeWParentDictionary))
return false;
if ((this == null && other != null) || (this != null && other == null))
return false;
if (this == null && other == null)
return true;
else
@SuppressWarnings("unchecked")
RBTreeWParentDictionary<K, V> oth = (RBTreeWParentDictionary<K, V>) other;
return equalsNodes(this.root, oth.root);
private boolean equalsNodes(RBTreeWParentNode<K, V> node1,
RBTreeWParentNode<K, V> node2)
if ((node1 == null && node2 != null) || (node1 != null && node2 == null))
return false;
else if (node1 == null && node2 == null)
return true;
else return node1.equals(node2)
&& equalsNodes(node1.getLeft(), node2.getLeft())
&& equalsNodes(node1.getRight(), node2.getRight());
RBTreeWParentNode.java(这里是RedBlackTree的节点)
package dictionary;
public class RBTreeWParentNode<K, V>
private K key;
private V value;
private boolean color;
private RBTreeWParentNode<K, V> left, right, parent;
private static final boolean RED = true;
private static final boolean BLACK = false;
public RBTreeWParentNode(K key, V value, RBTreeWParentNode<K, V> left,
RBTreeWParentNode<K, V> right, RBTreeWParentNode<K, V> parent)
this.key = key;
this.value = value;
this.color = RED;
this.left = left;
if (this.hasLeft())
this.getLeft().setParent(this);
this.right = right;
if (this.hasRight())
this.getRight().setParent(this);
this.parent = parent;
public RBTreeWParentNode(K key, V value)
this.key = key;
this.value = value;
this.color = RED;
public RBTreeWParentNode()
public K getKey()
return key;
public V getValue()
return value;
public boolean getColor()
return color;
public RBTreeWParentNode<K, V> getLeft()
return left;
public RBTreeWParentNode<K, V> getRight()
return right;
public RBTreeWParentNode<K, V> getParent()
return parent;
public RBTreeWParentNode<K, V> getBrother()
if (this.hasParent())
if (this.getParent().getLeft().equals(this))
return this.getParent().getRight();
else return this.getParent().getLeft();
else return null;
public boolean isRed()
return this.color == RED;
public boolean isBlack()
return this.color == BLACK;
public boolean hasLeft()
return this.getLeft() != null;
public boolean hasRight()
return this.getRight() != null;
public boolean hasParent()
return this.getParent() != null;
public boolean hasBrother()
if (this.hasParent())
if (this.getParent().getLeft().equals(this))
return this.getParent().getRight() != null;
else return this.getParent().getLeft() != null;
else return false;
public void setKey(K key)
this.key = key;
public void setValue(V value)
this.value = value;
public void setRed()
this.color = RED;
public void setBlack()
this.color = BLACK;
public void setParent(RBTreeWParentNode<K, V> node)
this.parent = node;
public void setLeft(RBTreeWParentNode<K, V> node)
this.left = node;
if (this.hasLeft())
this.left.setParent(this);
public void setRight(RBTreeWParentNode<K, V> node)
this.right = node;
if (this.hasRight())
this.right.setParent(this);
public void setColor(boolean color)
this.color = color;
@Override
public boolean equals(Object other)
if (!(other instanceof RBTreeWParentNode))
return false;
if ((this == null && other != null) || (this != null && other == null))
return false;
@SuppressWarnings("unchecked")
RBTreeWParentNode<K, V> oth = (RBTreeWParentNode<K, V>) other;
return checkFieldsEquals(oth);
private boolean checkFieldsEquals(RBTreeWParentNode<K, V> oth)
//Check keys
if ((this.getKey() == null && oth.getKey() != null)
|| (this.getKey() != null && oth.getKey() == null))
return false;
else
if ((this.getKey() == null && oth.getKey() == null)
|| this.getKey().equals(oth.getKey()))
if ((this.getValue() == null && oth.getValue() != null)
|| (this.getValue() != null && oth.getValue() == null))
return false;
else
if ((this.getValue() == null && oth.getValue() == null)
|| (this.getValue().equals(oth.getValue())))
if (this.getColor() != oth.getColor())
return false;
else
return (this.getKey() == null && oth.getKey() == null)
|| this.getKey().equals(oth.getKey());
else return false;
else
return false;
RBTreeWParentDictionaryTest.java -> My test class
2016 年 9 月 7 日更新 我已经更新了我的代码,因为我发现在修复之后我没有将节点光标更新为 root,并且我没有在删除的节点为黑色时才调用修复。 考虑到我的测试用例 testDeleteDoubles,我发现我选择了错误的候选人来切换要删除的项目,当它有一个兄弟时。 看到this simulator这个candidate应该是被删除item左分支的max节点,但不应该是后继,所以右分支的min item?
【问题讨论】:
我浏览了 FixDelete 代码,它似乎完全符合我的预期。我建议您对其进行保护并编写一些测试,看看您是否可以查明问题所在。 你好@sprinter 我已经更新了我的代码,因为我发现了一些问题,但我仍然无法修复它。我的猜测是,当我删除具有 2 个孩子的节点时,我选择了错误的继任者。你怎么看? 我认为没有快速解决方法。delete
方法似乎包含相当多的错误。例如,oldColor
应该在“两个孩子”的情况下更新,deleteFixUp
应该使用子节点调用,而不是节点本身。
感谢@dejvuth 的评论。你能更具体一点吗?与删除具有 2 个子节点的节点相比,必须采用 oldColor 的值是多少?我应该在哪个子节点上调用 deleteFixUp ?
【参考方案1】:
在delete()
中,需要记住被删除节点的子节点,因为删除后可能会违反红黑属性。假设我们声明RBTreeWParentNode<K, V> childOfDeletedNode;
然后,对于左孩子的情况,您更新childOfDeletedNode = node.getLeft();
对于右孩子的情况,你更新childOfDeletedNode = node.getRight();
对于两个孩子,您需要在调用min()
之后添加以下:
oldColor = node.getColor();
childOfDeletedNode = node.getLeft();
node.setColor(tmp.getColor());
对于叶子,带任何孩子childOfDeletedNode = node.getRight();
然后,用deleteFixUp(childOfDeletedNode);
固定子节点的颜色
现在由于childOfDeletedNode
可以是null
,您需要通过在循环条件中添加对node != null
的检查并在将颜色设置为黑色之前添加if 语句来处理deleteFixUp
中的这种情况最后一行。
不管怎样,你指的模拟器找到了左子树的最大节点。您的解决方案找到右子树的最小节点。两者都是正确的,但会导致不同的结果。你需要修复你的测试用例。
为了说明,删除前:
10(B)
/ \
8(R) 100(B)
/ \
5(B) 9(B)
/ \
2(R) 6(R)
8
被删除后,你将其替换为右子树的最小节点9
。颜色变为红色。
10(B)
/ \
9(R) 100(B)
/
5(B)
/ \
2(R) 6(R)
【讨论】:
以上是关于使用红黑树的字典 - 删除错误的主要内容,如果未能解决你的问题,请参考以下文章