java面试 –红黑树(插入删除过程详解)

 

目录

                       java面试 –红黑树(插入删除过程详解)

红黑树性质

左旋右旋

插入过程

1. 父为黑

2. 父为红

删除过程

定义

红黑树实际删除节点的性质

插入代码

删除代码

 

红黑树性质

红黑树是一种自平衡二叉树,在平衡二叉树的基础上每个节点又增加了一个颜色的属性,节点的颜色只能是红色或黑色。具有以下性质:

(1)节点要么是红色,要么是黑色,

(2)根节点只能是黑色;

(3)性质3.所有叶子都是黑色。(叶子是NUIL节点)

(4)红色节点的父节点和左右孩子节点都是黑色,及黑红相间;

(5)在任何一棵子树中,从根节点向下走到空节点的路径上所经过的黑节点的数目相同,从而保证了是一个平衡二叉树。

红黑树的操作

 

因为每一个红黑树也是一个特化的二叉查找树,因此红黑树上的查找操作与普通二叉查找树上的查找操作相同。然而,在红黑树上进行插入操作和删除操作会导致不 再符合红黑树的性质。恢复红黑树的属性需要少量(O(log n))的颜色变更(实际是非常快速的)和不超过三次树旋转(对于插入操作是两次)。 虽然插入和删除很复杂,但操作时间仍可以保持为 O(log n) 次 。

 

红黑树的优势

 

红黑树能够以O(log2(N))的时间复杂度进行搜索、插入、删除操作。此外,任何不平衡都会在3次旋转之内解决。这一点是AVL所不具备的。

而且实际应用中,很多语言都实现了红黑树的数据结构。比如 TreeMap, TreeSet(Java )、 STL(C++)等。

 

左旋右旋

 

插入过程

默认插入的结点为红色。为何? 
因为红黑树中黑节点至少是红节点的两倍,因此插入节点的父节点为黑色的概率较大,而此时并不需要作任何调整,因此效率较高。

1. 父为黑

 

插入后无需任何操作。由于黑节点个数至少为红节点的两倍,因此父为黑的情况较多,而这种情况在插入后无需任何调整,这就是红黑树比AVL树插入效率高的原因!

2. 父为红

父为红的情况破坏了红黑树的性质,此时需要根据叔叔的颜色来做不同的处理。 

 

叔叔为红 

 

此时很简单,只需交换爸爸、叔叔和爷爷的颜色即可。 
此时若爷爷节点和太爷爷节点颜色相同,再以爷爷节点为起始节点,进行刚才相同的操作,即:根据爷爷的兄弟颜色做相应的操作。

叔叔为黑 

  1. 爸爸在左、叔叔在右、我在左 

 

以爸爸为根节点,进行一次R旋转。

  1. 爸爸在左、叔叔在右、我在右 

 

先以我为根节点,进行一次L旋转; 
再以我为根节点,进行一次R旋转。

  1. 叔叔在左、爸爸在右、我在左 

 

先以我为根节点,进行一次R旋转; 
再以我为根节点,进行一次L旋转。

  1. 叔叔在左、爸爸在右、我在右 

 

以爸爸为根节点,进行一次L旋转。

删除过程

二叉搜索树的删除

若删除二叉搜索树的节点A,实际上删除的是二叉搜索树中序遍历的前驱节点,注意: 

1. 这个被删除节点要么就是一个叶子节点, 

2. 要么有且仅有一个左孩子 

然后将孩子顶替它原来的位置,最后将被删的节点值覆盖待删除的那个节点A。

 

红黑树按照二叉搜索树的方式删除节点,之后再进行相应的旋转操作,使得删除后的树仍然是一棵红黑树。

定义

  1. 待删除节点:要删除的那个节点
  2. 实际删除节点:待删除节点的中序遍历前驱

红黑树实际删除节点的性质

  1. 实际删除节点要么是叶子节点,要么有且仅有一个左孩子;
  2. 若为叶子节点,必为红色;
  3. 若实际删除节点还有孩子,则该必为左孩子; 
    a)若左孩子为红色,则实际删除节点必为黑色; 
    b)若左孩子为黑色,则实际删除节点红黑均可以。

 

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中情况:

 

 

插入代码

第一步 判读有没有根结点

第二步 如果父为黑 直接插入  

第三步 如果父为红 如果叔叔为红 父叔进行变色(函数),递归

第四步                    如果叔叔为黑 继续判断 父左叔右我左 R (函数)

                                                                      父左叔右我右 LR(函数)

                                                                      叔左父右我左 RL(函数)

                                                                      叔左父右我右 L (函数)

public class RBTree<T extends Comparable<T>> {

    public static void main(String[] args) {

        ///*
        int[] testNum = new int[]{15, 1, 3, 6, 8, 20, 22, 43, 67};
        RBTree<Integer> *** = new RBTree<Integer>();
        for (int i = 0; i < testNum.length; i++) {
          ***.insertNode(testNum[i]);
        }
        System.out.println(***.root.data);
        //*/
        /*
        RBTree<Character> *** = new RBTree<Character>();
        ***.insertNode('F');
        ***.insertNode('G');
        ***.insertNode('D');
        ***.insertNode('B');
        ***.insertNode('C');
        System.out.println(***.root.data);
        */
    }

    private static final boolean RED = true;
    private static final boolean BLACK = false;

    private TreeNode<T> root;

    /**
     * 插入一个新的节点,插入完毕后符合红黑树性质
     *
     * @param data  需要插入的数据
     */
    public void insertNode(T data) {
        TreeNode<T> curr;
        TreeNode<T> parent = toTargetParent(data);
        if (parent == null) {
            curr = root = new TreeNode<T>(data);
        } else {
            if (data.compareTo(parent.data) < 0) {
                curr = parent.left = new TreeNode<T>(data);
                curr.parent = parent;
            } else {
                curr = parent.right = new TreeNode<T>(data);
                curr.parent = parent;
            }
        }
        fixupTree(curr);
    }

    /**
     * 修复红黑树的不平衡状态
     *
     * @param node  新增节点
     */
    private void fixupTree(TreeNode node) {
        TreeNode parent = null, grandParent = null;
        boolean parentColor = false, uncleColor = false;
        if (node != root) {
            parent = node.parent;
            grandParent = parent.parent;
            parentColor = parent.color;
            uncleColor = getUncleColor(node);
        }
        //父节点为空表示其为根节点
        if (parent == null && node.color == RED) {
            node.color = BLACK;
        } else if (parentColor == RED && uncleColor == RED) {
            changeColor(grandParent);
            //再次判断根节点是否满足要求
            fixupTree(grandParent);
        } else if (parentColor == RED && uncleColor == BLACK) {
            dispatchRotation(grandParent, parent, node);
        }
    }

    /**
     * 判断属于哪种四种情况,LL、LR、RR、RL,使用正确的旋转
     *
     * @param grandParent   祖父节点
     * @param parent    父节点
     * @param child     新增节点
     */
    private void dispatchRotation(TreeNode grandParent, TreeNode parent, TreeNode child) {
        if (grandParent.left == parent) {
            if (parent.left == child) {
                rightRotation(grandParent);
            } else {
                leftRotation(parent);
                rightRotation(grandParent);
            }
        } else {
            if (parent.left == child) {
                rightRotation(parent);
                leftRotation(grandParent);
            } else {
                leftRotation(grandParent);
            }
        }
    }


    /**
     *  改变祖父节点以及两个子节点的颜色
     *
     * @param grandParent   传入新增节点的祖父
     */
    private void changeColor(TreeNode grandParent) {
        grandParent.color = RED;
        if (grandParent.left != null) {
            grandParent.left.color = BLACK;
        }
        if (grandParent.right != null) {
            grandParent.right.color = BLACK;
        }
    }

    /**
     * 返回叔叔节点的颜色
     *
     * @param node 一个节点
     * @return 其叔叔节点的颜色
     */
    private boolean getUncleColor(TreeNode node) {
        TreeNode parent = node.parent;
        //如果当前结点的祖父是空的,说明是其父节点是根节点
        return getBrotherColor(parent.parent == null ? node : parent);
    }

    /**
     * 返回兄弟节点的颜色
     *
     * @param child 传入节点
     * @return 返回兄弟节点的颜色
     */
    private boolean getBrotherColor(TreeNode child) {
        TreeNode parent = child.parent;
        if (parent.left == child && parent.right != null) {
            return parent.right.color;
        } else if (parent.right == child && parent.left != null) {
            return parent.left.color;
        } else {
            return BLACK;
        }
    }

    /**
     *  将传入的节点的右子节点提升为新的父节点,传入节点降为其右子节点
     *  注意颜色、父节点需要处理,务必要清除传入节点的右子节点,因为其已经被提升了父节点了。
     * @param curr  一个节点
     */
    private void leftRotation(TreeNode curr) {
        TreeNode tParent = curr.right;
        tParent.parent = curr.parent;
        tParent.color = BLACK;
        //新父节点的左子节点,放在传入节点的右边
        curr.right = tParent.left;
        if (tParent.left != null) {
            tParent.left.parent = curr;
        }
        //降为子节点前的数据整理
        curr.color = RED;
        curr.parent = tParent;
        tParent.left = curr;
        setChild(curr, tParent);
    }

    /**
     *  将传入的节点的左子节点提升为新的父节点,传入节点降为其右子节点
     *  注意颜色、父节点需要处理,务必要清除传入节点的左子节点,因为其已经被提升了父节点了。
     * @param curr  一个节点
     */
    private void rightRotation(TreeNode curr) {
        //新的父节点
        TreeNode tParent = curr.left;
        tParent.parent = curr.parent;
        tParent.color = BLACK;
        //新父节点的右子节点,放在传入节点的左边
        curr.left = tParent.right;
        if (tParent.right != null) {
            tParent.right.parent = curr;
        }
        //传入节点降为子节点前的数据整理
        curr.color = RED;
        curr.parent = tParent;
        tParent.right = curr;
        setChild(curr, tParent);
    }

    /**
     * 使旋转在树中生效
     *
     * @param roNode    被旋转的节点
     * @param newParent 被旋转之后的父节点
     */
    private void setChild(TreeNode roNode, TreeNode newParent) {
        TreeNode roNodeParent = newParent.parent;
        if (roNodeParent == null) {
            root = newParent;
        } else if (roNodeParent.left == roNode) {
            roNodeParent.left = newParent;
        } else {
            roNodeParent.right = newParent;
        }
    }

    /**
     *  调到data存放位置的父节点处
     * @param data  用于对比的数据
     * @return  data可存放处的父节点
     */
    private TreeNode<T> toTargetParent(T data) {
        TreeNode<T> curr = root;
        TreeNode<T> parent = root;
        while (curr != null) {
            parent = curr;
            if (data.compareTo(curr.data) < 0) {
                curr = curr.left;
            } else {
                curr = curr.right;
            }
        }
        return parent;
    }

    /**
     * 内部节点
     */
    static class TreeNode<T extends Comparable<T>> {
        T data;
        boolean color;
        //伪删除
        boolean isDeleted;
        TreeNode<T> left;
        TreeNode<T> right;
        TreeNode<T> parent;

        TreeNode(T data, boolean color) {
            this.data = data;
            this.color = color;
        }

        TreeNode(T data) {
            this.data = data;
            this.color = RED;
        }
    }
}

删除代码

/**
	 * 删除 叶子节点 后的修复过程
	 * @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());
	}

参考链接

https://www.cnblogs.com/LiaHon/p/11221634.html