红黑树随笔

红黑树


二叉搜索树

删除节点:

  1. 该节点是叶子节点
  2. 该节点有一个子节点
  3. 该节点有两个子节点
    在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

红黑树和平衡二叉搜索树

红黑树和平衡二叉搜索树有一定区别

二叉搜索树是强平衡二叉树
特点: 1) 左右子树的高度差不超过1
2) 左右两子树也是一颗平衡二叉树

红黑树是弱平衡二叉树
红黑树性质:

  1. 每一个节点要么是黑色要么是红色(颜色二选一)
  2. 根节点一定是黑色
  3. 叶子节点一定是黑色
  4. 红色节点的子节点一定是黑色(不可能有两个红色节点相连)
  5. 任意一节点到它下面的叶子节点的路径都包含相同数量的黑节点(俗称:黑高)
    - 5.1:如果已知一个节点存在了黑色子节点,那么该节点肯定有两个子节点

在这里插入图片描述

红黑树虽然不是完美平衡二叉搜索树,但是左右子树的黑色节点的层数是相同的,黑高是相同的(性质5),也即任意一个节点到叶子节点的路径都包含数量相同的黑色节点。
所以我们叫红黑树 的这种平衡为 黑色完美平衡

平衡二叉搜索树能自平衡是有 左左(右旋一次),右右(左旋一次),左右(先左旋,再右旋),右左(先右旋,在左旋)


红黑树

红黑树自平衡有三种操作:左旋,右旋和变色

红黑树插入:
新建插入节点一定是红色(因为红色相比于黑色更不容易破环红黑树的结构)

  1. 红黑树为空树
    - 此时第一个节点为根节点,要为黑色
  2. 插入元素已经存在
    - 则替代它(颜色也要和以前一样)
  3. 插入节点的父节点为黑节点
    - 由于插入的节点为红色,不会影响红黑树的平衡,不需要自平衡
    在这里插入图片描述
  4. 插入节点的父节点为红色
    可以推出父节点不是根节点,则插入节点肯定有爷爷,并且爷爷为黑色
    4.1. 叔叔节点存在并且为红色节点
    - 把爸爸和叔叔变成黑色,把爷爷变成红色
    -在这里插入图片描述

4.2 :叔叔节点不存在或为黑色节点,并且插入节点的父亲是爷爷节点的左子节点

在这里插入图片描述

4.2.1:当插入节点 I 为P节点的左边
一个右旋在这里插入图片描述

4.2.2:当插入节点 I 为P节点的右边
先左旋,
自己 I 变黑,爷爷PP变红
再右旋

在这里插入图片描述

4.2 :叔叔节点不存在或为黑色节点,并且插入节点的父亲是爷爷节点的右子节点
在这里插入图片描述

4.3.1:当插入节点 I 为P节点的右边(RR红色情况)
PP变红,P变黑,I变黑
以P节点左旋
在这里插入图片描述

4.3.2:当插入节点 I 为P节点的左边(RL红色情况)
对P右旋,PP,P变色,以PP为节点左旋

在这里插入图片描述

练习:
在这里插入图片描述

代码实现红黑树


public class RBTree<K extends Comparable<K>,V>{
	private static final boolean RED=true;
	private static final boolean BLACK=false;
	
	/**
	 * 树根的引用
	 */
	private RBNode root;
	
	
	
	public RBNode getRoot() {
		return root;
	}
	public void setRoot(RBNode root) {
		this.root = root;
	}
	/**
	 * 中序打印二叉树
	 */
	public void inOrderPrint() {
		inOrderPrint(root);
	}
	private void inOrderPrint(RBNode node) {
		if(node!=null) {
			inOrderPrint(node.left);
			System.out.println("key:"+node.key+",value:"+node.value);
			inOrderPrint(node.right);
		}
	}

	
	/**
	 * 设置节点为红色
	 */
	
	private void setRed(RBNode node) {
		if(node!=null) {
			node.color=RED;
		}
	}

	/**
	 * 设置节点为黑色
	 */
	
	private void setBlack(RBNode node) {
		if(node!=null) {
			node.color=BLACK;
		}
	}
	
	/**
	 * 节点是否为红色
	 */
	private boolean isRed(RBNode node) {
		if(node!=null) {
			return node.color == RED;
		}
		return false;
	}

	/**
	 * 节点是否为黑色
	 */
	private boolean isBlack(RBNode node) {
		if(node!=null) {
			return node.color == BLACK;
		}
		return false;
	}
	
	/**
	   *    获取当前节点的父节点
	 */
	private RBNode parentOf(RBNode node) {
		if(node!=null) {
			return node.parent;
		}
		return null;
	}
	
	/**
	 * 公开的输入方法
	 */
	public void insert(K key,V value) {
		RBNode node=new RBNode();
		node.setKey(key);
		node.setValue(value);
		//新节点一定时红色
		node.setColor(RED);
		insert(node);
	}
	
	private void insert(RBNode node) {
		//第一步:查找当前node的父节点
		RBNode parent = null;
		RBNode x = this.root;
		
		while(x != null) {
			parent = x;
			
			//cmp > 0 说明node.key 大于 x.key 需要到x的右子树查找
			//cmp == 0 说明node.key 等于x。key 说明需要进行替换操作
			//cmp < 0 说明node.key 小于x.key 需要到x的左子树查找
			int cmp=node.key.compareTo(x.key);
			if(cmp>0) {
				x=x.right;
			}else if(cmp==0) {
				x.setValue(node.getValue());
				return;
			}else {
				x=x.left;
			}
		}
		node.parent = parent;
		
		if(parent!=null) {
			//判断node与parent的key谁大
			int cmp=node.key.compareTo(parent.key);
			if(cmp>0) {//当前node的key比parent的key大,需要把node放入parent的右子节点
				parent.right = node;
			}else {//当前node的key比parent的key小,需要把node放入parent的左子节点
				parent.left=node;
			}
		}else {
			this.root=node;
		}
		
		//需要调用修复红黑树平衡的方法
		insertFixup(node);
	}
	
	   /**
	        * 插入后修复红黑树平衡的方法
	* |---情景1:如果红黑树为空树,需要将根节点染为黑色
	* |---情景2:如果插入节点的key已经存在,(这种情况不需要处理,因为修改树中的值不会触发红黑树修复平衡方法)
	* |---情景3:如果插入节点的父节点为黑色,这种情况不需要处理,(参考红黑树的性质4和性指5去理解)
	* (因为所插入的路径中,黑色节点数没发生变化,所以红黑树依然平衡)
	*  
	* 情景4 需要去处理的情景
	* |---情景4:插入节点的父节点为红色,(违反红黑树性质4,不能有两个红色节点相连)
	* 	|---情景4.1:叔叔节点存在,并且为红色(父-叔 双红)
	* 	处理:将爸爸和叔叔染成黑色,将爷爷染成红色,并且再以爷爷节点为当前节点,进行下一轮处理
	* 
	* 	|---情景4.2:叔叔节点不存在,或者为黑色,父节点为爷爷节点的左子树
	* 	处理:
	* 		|---情景4.2.1:插入节点为其父节点的左子节点(LL情况)
	*		 处理:将父节点染为黑色,将爷爷染为红色,然后以爷爷节点右旋即可
	* 		|---情景4.2.2:插入节点为其父节点的右子节点(LR情况)
	* 		处理:将父节点进行一次左旋,得到LL双红情景(4.2.1),然后指定父节点为当前节点进行下一轮处理
	* 
	* 	|---情景4.3:叔叔节点不存在,或者为黑色,父节点为爷爷节点的右子树
	* 		|---情景4.3.1:插入节点为其父节点的右子节点(RR情况)
	*		处理:将父节点染为黑色,将爷爷节点染为红色,然后以爷爷节点左旋即可
	* 		|---情景4.3.2:插入节点为其父节点的左子节点(RL情况)
	* 		处理:以父节点进行一次右旋,得到RR双红情景(4.3.1),然后指定父节点为当前节点进行下一轮处理
	*/
	
	private void insertFixup(RBNode node) {
		this.root.setColor(BLACK);
		
		RBNode parent = parentOf(node);
		RBNode gparent = parentOf(parent);
		
		//情景4:插入节点的父节点为红色
		if(parent!=null && isRed(parent)) {
			//如果父节点是红色,那么一定存在爷爷节点,因为根节点不可能是红色
			RBNode uncle=null;
			
			
			//父节点为爷爷节点的左子树
			if(parent==gparent.left) {				
				uncle = gparent.right;
				
				//情景4.1:叔叔存在,并且为红色(父-叔  双红)
				if(uncle != null && isRed(uncle)) {
					//将爸爸和叔叔染色为黑色,将爷爷染色为红色,并且再以爷爷节点为当前节点  进行下一轮处理
					setBlack(parent);
					setBlack(uncle);
					setBlack(gparent);
					insertFixup(gparent);
					return;
				}
	
				
				//情景4.2:叔叔不存在,或者为黑色,父节点为爷爷节点的左子树
				if(uncle == null || isBlack(uncle)) {
					
					//情景4.2.1:插入节点为其父节点的左子节点(LL情况)
					//将父节点染为黑色,将爷爷染为红色,然后以爷爷节点右旋即可
					if(node == parent.left) {
						setBlack(parent);
						setRed(gparent);
						rightRotate(gparent);
						return ;
						
					}
					
					//情景4.2.2:插入节点为其父节点的右子节点(LR情况)
					//将父节点进行一次左旋,得到LL双红情景(4.2.1),然后指定父节点为当前节点进行下一轮处理
					if(node == parent.right){
						leftRotate(parent);
						insertFixup(parent);
						return ;
					}
					
				}
			}else {//父节点为爷爷节点的右子树
				
				uncle=gparent.left;
				
				//情景4.1:叔叔存在,并且为红色(父-叔  双红)
				if(uncle != null && isRed(uncle)) {
					//将爸爸和叔叔染色为黑色,将爷爷染色为红色,并且再以爷爷节点为当前节点  进行下一轮处理
					setBlack(parent);
					setBlack(uncle);
					setBlack(gparent);
					insertFixup(gparent);
					return;
				}
				
				//情景4.3:叔叔节点不存在,或者为黑色,父节点为爷爷节点的右子树
				if(uncle!=null || isBlack(uncle)) {
					
					//情景4.3.1:插入节点为其父节点的右子节点(RR情况)
					//将父节点染为黑色,将爷爷节点染为红色,然后以爷爷节点左旋即可
					if(node == parent.right) {
						setBlack(parent);
						setRed(gparent);
						leftRotate(gparent);
						return ;
					}
					
					//情景4.3.2:插入节点为其父节点的左子节点(RL情况)
					//处理:以父节点进行一次右旋,得到RR双红情景(4.3.1),然后指定父节点为当前节点进行下一轮处理
					if(node == parent.left) {
						rightRotate(parent);
						insertFixup(parent);
						return;
					}
					
				}
				
			}
			
			
		}
		
	}
	
	
	
	
	
    /**
	     * 左旋方法
	     * 左旋示意图:左旋x节点
     *   p                   p
     *   |                   |
     *   x                   y
     *  / \      ---->      / \
     * lx  y               x   ry
     *    / \             / \
     *   ly  ry          lx  ly
     *
                * 左旋做了几件事?
     * 1.将x的右子节点指向y的左子节点(ly),并且把y的左子节点更新为x
     * 2.当x的父节点(不为空时),更新y的父节点为x的父节点,并将x的父节点 指定 子树(当前x的子树位置) 指定为y
     * 3.将x的父节点更新为y,将y的左子节点更新为x
     */
	private void leftRotate(RBNode x) {
		RBNode y=x.left;
		//1.将x的右子节点直接向y的左子节点(ly),将y的左子节点更新为x
		x.right=y.left;
		if(y.left != null) {
			y.left.parent=x;
		}
		
		//2.当x的父节点(不为空时),更新y的父节点为x的父节点,并将x的父节点 指定 子树(当前x的子树位置)指定为y
		if(x.parent!=null) {
			y.parent=x.parent;
			if(x==x.parent.left) {
				x.parent.left=y;
			}else {
				x.parent.right=y;
			}
		}else {//说明x是根节点,此时需要更新y为根节点引用
			this.root=y;
			this.root.parent = null;
		}
		
		//3.将x的父节点更新为y,将y的左子节点更新为x
		x.parent=y;
		y.left=x;
	}
	
	
	 /**
	     * 右旋方法
	     * 右旋示意图:右旋y节点
     *
     *     p                       p
     *     |                       |
     *     y                       x
     *    / \          ---->      / \
     *   x   ry                  lx  y
     *  / \                         / \
     * lx  ly                      ly  ry
     *
               * 右旋都做了几件事?
     * 1.将y的左子节点指向x的右子节点,并且更新x的右子节点的父节点为y
     * 2.当y的父节点不为空时,更新x的父节点为y的父节点,更新y的父节点的指定子节点(y当前位置) 为x
     * 3.更新y 的父节点为x ,更新x 的右子节点为y
     */
	private void rightRotate(RBNode y) {
		RBNode x=y.left;
		//1.将y的左子节点指向x的右子节点,并且更新x的右子节点的父节点为y
		if(x.right!=null) {
			y.left=x.right;
		}
		
		//当y的父节点不为空时,更新x的父节点为y的父节点,更新y的父节点的指定子节点(y当前的位置)为x;
		if(y.parent!=null) {
			x.parent=y.parent;
			if(y.parent.left==y) {
				y.parent.left=x;
			}else {
				y.parent.right=x;
			}
		}else {//说明x是根节点,此时需要更新y为根节点引用
			this.root=x;
			this.root.parent = null;
		}
		
		//3.更新y 的父节点为x ,更新x 的右子节点为y
		y.parent=x;
		x.right=y;
	}
	
	
	static class RBNode<K extends Comparable<K>,V>{
		private RBNode parent;
		private RBNode left;
		private RBNode right;
		private boolean color;
		private K key;
		private V value;
		
		public RBNode() {
			super();
		}

		public RBNode(RBNode parent, RBNode left, RBNode right, boolean color, K key, V value) {
			this.parent = parent;
			this.left = left;
			this.right = right;
			this.color = color;
			this.key = key;
			this.value = value;
		}

		public RBNode getParent() {
			return parent;
		}

		public void setParent(RBNode parent) {
			this.parent = parent;
		}

		public RBNode getLeft() {
			return left;
		}

		public void setLeft(RBNode left) {
			this.left = left;
		}

		public RBNode getRight() {
			return right;
		}

		public void setRight(RBNode right) {
			this.right = right;
		}

		public boolean isColor() {
			return color;
		}

		public void setColor(boolean color) {
			this.color = color;
		}

		public K getKey() {
			return key;
		}

		public void setKey(K key) {
			this.key = key;
		}

		public V getValue() {
			return value;
		}

		public void setValue(V value) {
			this.value = value;
		}	
	}
}

TreeOperation()



/**
 * @Auther: csp1999
 * @Date: 2020/11/09/15:10
 * @Description: 打印红黑树的工具类
 */
public class TreeOperation {
    /*
           树的结构示例:
              1
            /   \
          2       3
         / \     / \
        4   5   6   7
    */

    // 用于获得树的层数
    public static int getTreeDepth(RBTree.RBNode root) {
        return root == null ? 0 : (1 + Math.max(getTreeDepth(root.getLeft()), getTreeDepth(root.getRight())));
    }


    private static void writeArray(RBTree.RBNode currNode, int rowIndex, int columnIndex, String[][] res, int treeDepth) {
        // 保证输入的树不为空
        if (currNode == null) return;
        // 先将当前节点保存到二维数组中
        res[rowIndex][columnIndex] = String.valueOf(currNode.getKey() /*+ "-" + (currNode.isColor() ? "R" : "B") + ""*/);

        // 计算当前位于树的第几层
        int currLevel = ((rowIndex + 1) / 2);
        // 若到了最后一层,则返回
        if (currLevel == treeDepth) return;
        // 计算当前行到下一行,每个元素之间的间隔(下一行的列索引与当前元素的列索引之间的间隔)
        int gap = treeDepth - currLevel - 1;

        // 对左儿子进行判断,若有左儿子,则记录相应的"/"与左儿子的值
        if (currNode.getLeft() != null) {
            res[rowIndex + 1][columnIndex - gap] = "/";
            writeArray(currNode.getLeft(), rowIndex + 2, columnIndex - gap * 2, res, treeDepth);
        }

        // 对右儿子进行判断,若有右儿子,则记录相应的"\"与右儿子的值
        if (currNode.getRight() != null) {
            res[rowIndex + 1][columnIndex + gap] = "\\";
            writeArray(currNode.getRight(), rowIndex + 2, columnIndex + gap * 2, res, treeDepth);
        }
    }


    public static void show(RBTree.RBNode root) {
        if (root == null) System.out.println("EMPTY!");
        // 得到树的深度
        int treeDepth = getTreeDepth(root);

        // 最后一行的宽度为2的(n - 1)次方乘3,再加1
        // 作为整个二维数组的宽度
        int arrayHeight = treeDepth * 2 - 1;
        int arrayWidth = (2 << (treeDepth - 2)) * 3 + 1;
        // 用一个字符串数组来存储每个位置应显示的元素
        String[][] res = new String[arrayHeight][arrayWidth];
        // 对数组进行初始化,默认为一个空格
        for (int i = 0; i < arrayHeight; i++) {
            for (int j = 0; j < arrayWidth; j++) {
                res[i][j] = " ";
            }
        }

        // 从根节点开始,递归处理整个树
        // res[0][(arrayWidth + 1)/ 2] = (char)(root.val + '0');
        writeArray(root, 0, arrayWidth / 2, res, treeDepth);

        // 此时,已经将所有需要显示的元素储存到了二维数组中,将其拼接并打印即可
        for (String[] line : res) {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < line.length; i++) {
                sb.append(line[i]);
                if (line[i].length() > 1 && i <= line.length - 1) {
                    i += line[i].length() > 4 ? 2 : line[i].length() - 1;
                }
            }
            System.out.println(sb.toString());
        }
    }
}


测试:

	public static void main(String[] args) {

		
		 RBTree<String, Object> rbtree = new RBTree();
	        //测试输入:ijkgefhdabc
	        while(true) {
	            Scanner sc = new Scanner(System.in);
	            System.out.println("请输入key:");
	            String key = sc.next();

	            rbtree.insert(key, null);
	            TreeOperation.show(rbtree.getRoot());
	        }
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值