数据结构:红黑树的删除

数据结构:红黑树的删除

前面的文章我们已经介绍了红黑树的查找,插入,更新等操作。今天我们介绍红黑树的删除操作。网上介绍红黑树删除的文章很多,但是大部分都是抄来抄去的,很多连抄都没有抄清楚,有用的就那几篇,希望自己的这篇文章能成为有用的那部分吧!

红黑树也是一颗二叉排序树,节点的删除也是分为3种情况即,将要删除的节点没有子节点,将要删除的节点下有一个子节点,将要删除的节点下有两个子节点。有不了解的可以参考我们前面介绍二叉搜索树的文章,这里不再赘述。我们主要介绍各种不同情况下节点的修复是怎样操作的。在介绍之前我们再次回顾一下红黑树的5个特性,这非常重要,因为删除节点后,很可能会破坏这5个特性,我们的修复操作就是使它重新满足这5个特性。

  1. 所有节点都是红色或者黑色
  2. 根节点为黑色
  3. 所有的 NULL 叶子节点都是黑色
  4. 如果该节点是红色的,那么该节点的子节点一定都是黑色
  5. 所有的 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实现

编辑于 2019-03-11 14:55