手撕红黑树
1.什么是红黑树?
红黑树(Red Black Tree) 是一种自平衡二叉查找树,
在1972年由Rudolf Bayer发明的,当时被称为平衡二叉B树(symmetric binary B-trees)。后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的“红黑树”。
红黑树是一种特化的AVL树(平衡二叉树),都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。与二叉树相比,在TreeNode中加了color属性,来表示该结点是黑色还是红色,还有parent属性,来表示当前结点的父节结点。
2.红黑树的五大性质:
①:结点是红色或黑色。
②:根结点是黑色。
③:所有叶子结点都是黑色(就是NIL结点,注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!)。
④:每个红色结点的两个子节点都是黑色(并且从根结点到叶子结点的路径中不能有两个连续的红色结点,就是当前结点为红色结点时,其左子节点和右子结点都不能为红色)。
⑤:从任一结点到其叶子结点的所有路径都包含相同数目的黑色结点。
如下图所示,90->60->50->30->NIL , 90->60->50->NIL,90->120->110->NIL都是只有4个黑色结点.
重点
3.红黑树的基本操作(一):
首先,先确定5个后续后用到的变量
curNode:表示当前结点
right:表示当前结点的右子结点,即curNode.right
left:当前结点的左子结点,即curNode.left
parentNode:当前结点的父结点,即curNode.parent
grandpaNode:当前结点的爷爷结点 ,即curNode.parent.parent
①:变色------>指结点的颜色由黑变红或者红变黑。
②:左旋转----->
- 将curNode 的右子结点指向当前结点的右子结点的左子结点(right.left),同时更新right.left结点的父结点。
RBNode right = curNode.right;
curNode.right = right.left;
if(right.left!=null){
right.left.parent = curNode;
}
- 将 right 的父结点(即right.parent)更新为curNode的父结点,绑定curNode的父结点与right结点的联系
//2.当 curNode 的父结点(即curNode.parent,不为null)时 --> curNode不是根结点
if(curNode.parent!=null){
// 将 right 的父结点(即right.parent)更新为curNode的父结点
right.parent = curNode.parent;
//绑定curNode的父结点与right结点的联系
if(curNode.parent.left==curNode){
//当curNode是它父结点的左子结点时
curNode.parent.left = right;
}else{
//当curNode是它父结点的右子结点时
curNode.parent.right = right;
}
}else{
//2.当 curNode 的父结点(即curNode.parent,为null)时 --> curNode是根结点
this.root = right;
this.root.parent = null;
}
- 将当前结点的父结点更新为right,将right的左子结点更新为curNode
curNode.parent = right;
right.left = curNode;
③:右旋转
- 将curNode的左子结点指向当前结点的左子结点的右子结点(left.right),并更新left.right结点的父结点
RBNode left = curNode.left;
curNode.left = left.right;
if(left.right!=null){
left.right.parent = curNode.parent;
}
- 将 left 的父结点(即left.parent)更新为curNode的父结点,绑定curNode的父结点与left结点的联系
if(curNode.parent!=null){
// 将 left 的父结点(即left.parent)更新为curNode的父结点
left.parent = curNode.parent;
//绑定curNode的父结点与right结点的联系
if(curNode==curNode.parent.left){
//当curNode是它父结点的左子结点时
curNode.parent.left = left;
}else{
//当curNode是它父结点的右子结点时
curNode.parent.right = left;
}
}else{
//2.当 curNode 的父结点(即curNode.parent,为null)时 --> curNode是根结点
this.root = left;
this.root.parent = null;
}
- 将当前结点的父结点更新为left,将left的右子结点更新为curNode
curNode.parent = left;
left.right = curNode;
如果左旋转和右旋转概念还比较模糊,建议自己给自己出题,练习一下左旋转和右旋转
红黑树的基本操作(二):添加结点
在进行后续操作之前,先做一个约定
插入的结点为什么默认为红色?
首先,红黑树的性质之一:从任一结点到其叶子结点的所有路径都包含相同数目的黑色结点。
- 假如任意插入的节点是黑色节点,则连续插入两个黑色节点后,就不满足红黑树的性质(肯定有一边的黑色节点多于另外一边),插入结点可以看成是替换其父结点的NIL结点,因为插入结点的左、右子结点都为null,每次插入都会有在自带两个NIL结点,从而使这条路径上的黑色结点比其他路径的黑色结点数要多,导致失衡;
- 但是连续插入两个红色节点就不会破坏性质四,因为连续两个红色节点可以根据红黑树的变换规则进行变色或者旋转的调整,以达到标准的红黑树!
添加结点时会遇到以下多个不同的情景
- 红黑树为空树
处理方法:将根结点的颜色染成黑色。 - 插入结点的key已经存在
处理方法:不需要处理了,进行值替换操作 - 插入结点的父结点为黑色
处理方法:不需要处理,因为不影响红黑树的平衡
插入结点的父结点为红色
-
叔叔结点存在,并且为红色时:
处理方法:将父结点和叔叔结点染成黑色,爷爷结点染成红色,再以爷爷结点为当前结点做下一步处理。
如图所示,C为插入节点,P是C的父结点,G是C的爷爷结点,U是C的叔叔结点。此时P是红色,U也是红色。
-
叔叔结点不存在或者为黑色时,父结点为爷爷结点的左子结点:
①:插入结点为父结点的左子结点(LL):
处理方法:将父结点染成黑色,爷爷结点染成红色,再以爷爷结点进行右旋。
②:插入结点为父结点的右子结点(LR):
处理方法:以父结点进行左旋转,得到(LL)情景,进行下一轮处理。
如图所示:
- 叔叔结点不存在或者为黑色时,父结点为爷爷结点的右子结点
①:插入结点为父结点的右子结点(RR)
处理方法:将父结点染成黑色,爷爷染成红色,再以爷爷结点为当前结点进行左旋转。
如图所示:
②:插入结点为父结点的左子结点(RL)
处理方法:以父结点进行右旋转,得到(RR)情景,进行下一轮处理。
如图所示:
JAVA代码实现:
public class RBTree <K extends Comparable<K>,V>{
private static final boolean RED = true;//红色
private static final boolean BLACK = false;//黑色
private RBNode root;//根结点
/**
* 获取当前结点的父节点
* @param node
* @return
*/
private RBNode getParent(RBNode node){
if(node==null) return null;
return node.parent;
}
/**
* 判断当前结点是否是红色,是放回true,否则false
* @param node
* @return
*/
private boolean isRED(RBNode node){
if (node == null) {
return false;
}
return node.color;
}
/**
* 将当前结点颜色设置为红色
* @param node
*/
private void setRed(RBNode node){
if (node == null) {
return;
}
node.color = true;
}
/**
* 将当前结点颜色设置为黑色
* @param node
*/
private void setBlack(RBNode node){
if (node == null) {
return;
}
node.color = false;
}
/**
* 中序遍历
*/
public void inOrderPrint(){
if(this.root==null)return;
inOrderPrint(this.root);
}
private void inOrderPrint(RBNode node){
if(node.left!=null) inOrderPrint(node.left);
System.out.println("key:"+node.key+" , value:"+node.value);
if(node.right!=null) inOrderPrint(node.right);
}
/**
*对外公开的插入结点的接口
*/
public void insert(K key,V value){
RBNode node = new RBNode();
node.setKey(key);
node.setValue(value);
node.color = RED;
insert(node);
}
private void insert(RBNode node){
RBNode parent = null;
RBNode cur = this.root;
//查找node的父结点
while(cur!=null){
parent = cur;
int cmp = cur.key.compareTo(node.getKey());
//cmp==0 说明key值相等需要进行替换操作
//cmp>0 说明cur的key值大于node的key值,向左子树查找
//cmp<0 说明cur的key值小于node的key值,向右子树查找
if(cmp==0){
cur.setValue(node.getValue());
return;
}else if(cmp>0){
cur = cur.left;
}else{
cur = cur.right;
}
}
node.parent = parent;
if (parent != null) {
int cmp = node.key.compareTo(parent.getKey());
if(cmp>0){
//cmp大于0说明当前结点比其父结点的key值大,是父结点的右子结点
parent.right = node;
}else{
//cmp小于0说明当前结点比其父结点的key值小,是父结点的左子结点
parent.left = node;
}
}
RestoreTreeBalance(node);
}
/**
* 1.红黑树为空树--->将root的颜色设置成黑色
* 2.插入结点的key已经存在 ---> insert方法已经处理
* 3.插入结点的父结点为黑色---> 不需要处理,因为不影响红黑树的平衡
* 4:插入结点的父结点为红色
* ①:叔叔结点存在,并且为红色时---->将父结点和叔叔结点染成黑色,爷爷结点染成红色,再以爷爷结点为当前结点做下一步处理
* ②:叔叔结点不存在或者为黑色时,父结点为爷爷结点的左子结点
* 1.插入结点为父结点的左子结点(LL)--->将父结点染成黑色,爷爷结点染成红色,再以爷爷结点进行右旋
* 2.插入结点为父结点的右子结点(LR)--->以父结点进行左旋转,得到(LL)情景,进行下一轮处理
* ③:叔叔结点不存在或者为黑色时,父结点为爷爷结点的右子结点
* 1.插入结点为父结点的右子结点(RR)--->将父结点染成黑色,爷爷染成红色,再以爷爷结点为当前结点进行左旋转
* 2.插入结点为父结点的左子结点(RL)--->以父结点进行右旋转,得到(RR)情景,进行下一轮处理
*/
private void RestoreTreeBalance(RBNode node){
//1.红黑树为空树--->将root的颜色设置成黑色
if(this.root==null) {
root = node;
node.color = BLACK;
return;
}
RBNode parentNode = getParent(node);//父结点
RBNode grandpaNode = getParent(parentNode);//爷爷结点
if(parentNode!=null&&isRED(parentNode)){
//获取node的叔叔结点
RBNode uncleNode = null;
if(parentNode==grandpaNode.left){
uncleNode = grandpaNode.right;
}else{
uncleNode = grandpaNode.left;
}
if(uncleNode!=null&&isRED(uncleNode)){
//插入结点的父结点为红色:①:叔叔结点存在,并且为红色时---->将父结点和叔叔结点染成黑色,爷爷结点染成红色,再以爷爷结点为当前结点做下一步处理
setBlack(parentNode);
setBlack(uncleNode);
setRed(grandpaNode);
RestoreTreeBalance(grandpaNode);
}else{
//插入结点的父结点为红色:②:叔叔结点不存在或者为黑色时,父结点为爷爷结点的左子结点
if(parentNode==grandpaNode.left){
//1.插入结点为父结点的左子结点(LL)--->将父结点染成黑色,爷爷结点染成红色,再以爷爷结点进行右旋
if(parentNode.left==node){
setBlack(parentNode);
setRed(grandpaNode);
RightRotation(grandpaNode);
}else{
//2.插入结点为父结点的右子结点(LR)--->以父结点进行左旋转,得到(LL)情景,进行下一轮处理
LeftRotation(parentNode);
RestoreTreeBalance(parentNode);
}
}else{
//插入结点的父结点为红色:②:叔叔结点不存在或者为黑色时,父结点为爷爷结点的右子结点
if(parentNode.right==node){
//1.插入结点为父结点的右子结点(RR)--->将父结点染成黑色,爷爷染成红色,再以爷爷结点为当前结点进行左旋转
setBlack(parentNode);
setRed(grandpaNode);
LeftRotation(grandpaNode);
}else{
//2.插入结点为父结点的左子结点(RL)--->以父结点进行右旋转,得到(RR)情景,进行下一轮处理
RightRotation(parentNode);
RestoreTreeBalance(parentNode);
}
}
}
}
}
/**
* 左旋转
*
* @param curNode
*/
private void LeftRotation(RBNode curNode){
//1.将curNode的右子结点指向当前结点的右子结点的左子结点(right.left),并更新right.left结点的父结点
RBNode right = curNode.right;
curNode.right = right.left;
if(right.left!=null){
right.left.parent = curNode;
}
//2.当 curNode 的父结点(即curNode.parent,不为null)时 --> curNode不是根结点
if(curNode.parent!=null){
// 将 right 的父结点(即right.parent)更新为curNode的父结点
right.parent = curNode.parent;
//绑定curNode的父结点与right结点的联系
if(curNode.parent.left==curNode){
//当curNode是它父结点的左子结点时
curNode.parent.left = right;
}else{
//当curNode是它父结点的右子结点时
curNode.parent.right = right;
}
}else{
//2.当 curNode 的父结点(即curNode.parent,为null)时 --> curNode是根结点
this.root = right;
this.root.parent = null;
}
//3.将当前结点的父结点更新为right,将right的左子结点更新为curNode
curNode.parent = right;
right.left = curNode;
}
/**
* 右旋转
* @param curNode
*/
private void RightRotation(RBNode curNode){
//1.将curNode的左子结点指向当前结点的左子结点的右子结点(left.right),并更新left.right结点的父结点
RBNode left = curNode.left;
curNode.left = left.right;
if(left.right!=null){
left.right.parent = curNode.parent;
}
//2.当 curNode 的父结点(即curNode.parent,不为null)时 --> curNode不是根结点
if(curNode.parent!=null){
// 将 left 的父结点(即left.parent)更新为curNode的父结点
left.parent = curNode.parent;
//绑定curNode的父结点与right结点的联系
if(curNode==curNode.parent.left){
//当curNode是它父结点的左子结点时
curNode.parent.left = left;
}else{
//当curNode是它父结点的右子结点时
curNode.parent.right = left;
}
}else{
//2.当 curNode 的父结点(即curNode.parent,为null)时 --> curNode是根结点
this.root = left;
this.root.parent = null;
}
//3.将当前结点的父结点更新为left,将left的右子结点更新为curNode
curNode.parent = left;
left.right = curNode;
}
//静态内部类,结点类
public 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(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() {
}
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;
}
}
}