数据结构:红黑树的删除
前面的文章我们已经介绍了红黑树的查找,插入,更新等操作。今天我们介绍红黑树的删除操作。网上介绍红黑树删除的文章很多,但是大部分都是抄来抄去的,很多连抄都没有抄清楚,有用的就那几篇,希望自己的这篇文章能成为有用的那部分吧!
红黑树也是一颗二叉排序树,节点的删除也是分为3种情况即,将要删除的节点没有子节点,将要删除的节点下有一个子节点,将要删除的节点下有两个子节点。有不了解的可以参考我们前面介绍二叉搜索树的文章,这里不再赘述。我们主要介绍各种不同情况下节点的修复是怎样操作的。在介绍之前我们再次回顾一下红黑树的5个特性,这非常重要,因为删除节点后,很可能会破坏这5个特性,我们的修复操作就是使它重新满足这5个特性。
- 所有节点都是红色或者黑色
- 根节点为黑色
- 所有的 NULL 叶子节点都是黑色
- 如果该节点是红色的,那么该节点的子节点一定都是黑色
- 所有的 NULL 节点到根节点的路径上的黑色节点数量一定是相同的
下面我们开始讨论修复操作(下面的叶子节点都是指非NULL的叶子节点):
A. 删除的是叶子节点且该叶子节点是红色的 ---> 无需修复,因为它不会破坏红黑树的5个特性
B. 删除的是叶子节点且该叶子节点是黑色的 ---> 很明显会破坏特性5,需要修复。
C. 删除的节点(为了便于叙述我们将其称为P)下面有一个子节点 S,对于这种情况我们通过 将P和S的值交换的方式,巧妙的将删除P变为删除S,S是叶子节点,这样C这种情况就会转 换为A, B这两种情况:
C1: P为黑色,S为红色 ---> 对应 A 这种情况
C2: P为黑色或红色,S为黑色 --- > 对应 B 这种情况
D. 删除的节点有两个子节点,对于这种情况,我们通过将P和它的后继节点N的值交换的方 式,将删除节点P转换为删除后继节点N,而后继节点只可能是以下两种情况:
D1: N是叶子节点 --- > 对应情况 A 或 B
D2: N有一个子节点 ---- > 对应情况 C
所以通过上面的分析我们发现,红黑树节点删除后的修复操作都可以转换为 A 或 B这两种情况,而A不需要修复,所以我们只需要研究B这种情况如何修复就行了。
下面我们讨论如何修复B中情况:
分析完后,下面我们代码实现:
/**
* 删除 叶子节点 后的修复过程
* @param deletedNode 被删除的节点
* @param deletedNodeParent 被删除节点的父节点
*/
private void deleteLeafFix(Node deletedNode){
while((deletedNode != root) && (BLACK == deletedNode.color)){
Node parent = deletedNode.parentNode;
Node brother = getBrother(deletedNode);
if(deletedNode.key.compareTo(parent.key) > 0){ // 删除的是右叶子节点
if(RED == brother.color){ // case5: 如果该兄弟节点是红色的,那么根据红黑树的特性可以得出它的一定有两个黑色的子节点
brother.color = BLACK;
brother.rightNode.color = RED;
rightRotation(parent);
break;
}else{
if((null == brother.leftNode) && (null == brother.rightNode)){ // case4: 兄弟节点是黑色的,且没有子节点
brother.color = RED; // 将兄弟节点设为红色,将父节点设为当前节点递归, 直到根节点,或遇到红色节点,
deletedNode = parent;
}else{
if((null != brother.leftNode) && (RED == brother.leftNode.color)){// case1: 兄弟节点是黑色的,且有一个左节点(可以断定 左节点是红色的)
//case3: 兄弟节点是黑色的,且有两个节点(可以断定 左右节点都是红色的) 这个和情况 1 是一样的
brother.color = parent.color;
parent.color = BLACK;
brother.leftNode.color = BLACK;
rightRotation(parent);
break;
}else{// case2: 兄弟节点是黑色的,且有一个右节点(可以断定 右节点是红色的)
brother.rightNode.color = BLACK;
brother.color = RED;
leftRotation(brother);
}
}
}
}else{// 删除的是左叶子节点
if(RED == brother.color){ // case5 : 如果该兄弟节点是红色的,那么根据红黑树的特性可以得出它的一定有两个黑色的子节点
brother.color = BLACK;
brother.leftNode.color = RED;
leftRotation(parent);
break;
}else{
if((null == brother.leftNode) && (null == brother.rightNode)){ // case4: 兄弟节点是黑色的,且没有子节点
brother.color = RED; // 将兄弟节点设为红色,将父节点设为当前节点递归, 直到根节点,或遇到红色节点,
deletedNode = parent;
}else{
if((null != brother.rightNode) && (RED == brother.rightNode.color)){ // case1 : 兄弟节点是黑色的,且有一个右节点(可以断定 右节点是红色的)
// case3 : 兄弟节点是黑色的,且有两个节点(可以断定 左右节点都是红色的) 这个和情况 1 是一样的
brother.color = parent.color;
parent.color = BLACK;
brother.rightNode.color = BLACK;
leftRotation(parent);
break;
}else{ // case2: 兄弟节点是黑色的,且有一个左节点(可以断定 左节点是红色的)
brother.leftNode.color = BLACK;
brother.color = RED;
rightRotation(brother);
}
}
}
}
}
deletedNode.color = BLACK;
}
private Node getBrother(Node node){
if(null == node){
return null;
}
Node parent = node.parentNode;
if(null == parent){
return null;
}
if(node.key.compareTo(parent.key) > 0){
return parent.leftNode;
}else{
return parent.rightNode;
}
}
public boolean delete(K key){
if(null != key){
if(null != root){
return deleteNode(key, root, null);
}
}
return false;
}
private boolean deleteNode(K key, Node current, Node parent){
if(null != current){
if(key.compareTo(current.key) > 0){
return deleteNode(key, current.rightNode, current);
}
if(key.compareTo(current.key) < 0){
return deleteNode(key, current.leftNode, current);
}
if(key.compareTo(current.key) == 0){
if((null != current.leftNode) && (null != current.rightNode)){ //将要删除的节点下有两个子节点
dleTwoChildrenNode(current);
return true;
}else{
if((null == current.leftNode) && (null == current.rightNode)){ //将要删除的节点没有子节点
deleteLeafFix(current);
if(current.key.compareTo(parent.key) > 0){
parent.rightNode = null;
}else{
parent.leftNode = null;
}
return true;
}else{ // 将要删除的节点下有一个子节点,
dleOneChildNode(current);
return true;
}
}
}
}
return false;
}
private void dleOneChildNode(Node delNode){
Node replaceNode = (null == delNode.leftNode) ? delNode.rightNode : delNode.leftNode;
deltetLeafNode(delNode, replaceNode);
}
/**
* 处理被删除节点有两个子节点的情况
* @param target 将要被删除的节点
*/
private void dleTwoChildrenNode(Node target){
Node replaceNode = successor(target);
if((null == replaceNode.rightNode) && (null == replaceNode.leftNode)){
deltetLeafNode(target, replaceNode);
}else{
target.key = replaceNode.key;
target.value = replaceNode.value;
dleOneChildNode(replaceNode);
}
}
private void deltetLeafNode(Node target, Node replaceNode){
target.key = replaceNode.key;
target.value = replaceNode.value;
deleteLeafFix(replaceNode);
if(replaceNode == replaceNode.parentNode.rightNode){
replaceNode.parentNode.rightNode = null;
}else{
replaceNode.parentNode.leftNode = null;
}
}
//找后继结点。即,查找"红黑树中数据值大于该结点"的"最小结点"
private Node successor(Node node) {
if (node == null){
return null;
}
if (null != node.rightNode) { // 获取 后继节点
Node p = node.rightNode;
while (null != p.leftNode){
p = p.leftNode;
}
return p;
} else {
Node p = node.parentNode;
Node ch = node;
while (p != null && ch == p.rightNode) {
ch = p;
p = p.parentNode;
}
return p;
}
}
public static void main(String[] args) {
RedBlackTree<Integer, String> bst = new RedBlackTree<Integer, String>();
bst.put(100, "v100");
bst.put(50, "v50");
bst.put(150, "v150");
bst.put(20, "v20");
bst.put(85, "v85");
bst.put(10, "v10");
bst.put(15, "a15");
bst.put(75, "v75");
bst.put(95, "v95");
bst.put(65, "v65");
bst.put(76, "v76");
bst.put(60, "v60");
bst.put(66, "v66");
bst.put(61, "v61");
// 当前节点是左节点 的 5中情况
//bst.delete(15); // 1. 兄弟节点是黑色的,且有一个右节点(可以断定 右节点是红色的)
// 2. 兄弟节点是黑色的,且有一个左节点(可以断定 左节点是红色的
//bst.put(140, "v140");
//bst.delete(95);
// 4. 兄弟节点是黑色的,且没有子节点
//bst.delete(66);
//5. 如果该兄弟节点是红色的,那么根据红黑树的特性可以得出它的一定有两个黑色的子节点
//bst.delete(95);
//bst.delete(15);
System.out.println(bst.getRoot());
}
建议代码和分析的图片对照阅读。
你可以在 这里获取 完整源代码 红黑树Java实现