R-B Tree简介
R-B Tree,全称是Red-Black Tree,又称为“红黑树”,它一种特殊的二叉查找树。红黑树的每个节点上都有存储位表示节点的颜色,可以是红(Red)或黑(Black)。
红黑树的特性:
(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙外部节点的所有路径上包含相同数目的黑节点。
注意:
(01) 特性(3)中的叶子节点,是只为空(NIL或null)的节点。
(02) 特性(5),确保没有一条路径会比其他路径长出俩倍。因而,红黑树是相对是接近平衡的二叉树。
红黑树的基本操作 (左旋和右旋)
想要对红黑树添加、删除。在对红黑树进行添加或删除之后,都会用到旋转方法。为什么呢?道理很简单,添加或删除红黑树中的节点之后,红黑树就发生了变化,可能不满足红黑树的性质,也就不再是一颗红黑树了,而是一颗普通的树。而通过旋转,可以使这颗树重新成为红黑树。简单点说,旋转的目的是让树保持红黑树的特性。
旋转包括两种:左旋 和 右旋。下面分别对它们进行介绍
左旋
z
x / \
/ \ --(左旋)--> x b
y z / \
/ \ y a
a b
右旋
y
x / \
/ \ --(右旋)--> a x
y z / \
/ \ b z
a b
理解了左旋和右旋就可以对红黑树添加、删除后,做平衡修复
(一)、添加节点
插入后修复红黑树平衡的方法
* |---情景1:红黑树为空树
根节点变黑
* |---情景2:插入节点的key已经存在
插入节点值,赋值给已存在节点
* |---情景3:插入节点的父节点为黑色
父节点为黑色,直接插入
红黑树插入难点,插入节点的父节点为红色,分以下情况
* |---情景4:插入节点的父节点为红色
* |---情景4.1:叔叔节点存在,并且为红色(父-叔 双红)
叔叔节点存在,并为红,叔叔和父节点变黑,爷节点变红,以爷爷节点为插入节点进行修复
* |---情景4.2:叔叔节点不存在,或者为黑色,父节点为爷爷节点的左子树
* |---情景4.2.1:插入节点为其父节点的左子节点(LL情况)
父节点、爷节点改色,爷节点右旋
* |---情景4.2.2:插入节点为其父节点的右子节点(LR情况)
父节点左旋,插入节点、爷节点改色,爷右旋(父节点左旋,转化为LL情况)
* |---情景4.3:叔叔节点不存在,或者为黑色,父节点为爷爷节点的右子树
* |---情景4.3.1:插入节点为其父节点的右子节点(RR情况)
父节点、爷节点改色,爷节点右旋
* |---情景4.3.2:插入节点为其父节点的左子节点(RL情况)
父节点左旋,插入节点、爷节点改色,爷节点右旋
添加代码:
//添加
public void add(Node5 node){
Node5 parent = null;
Node5 r = root;
while (r != null){
parent = r;
if (r.key > node.key){
r = r.left;
} else if (r.key == node.key){
System.out.println("值已修改");
return;
} else {
r = r.right;
}
}
node.parent = parent;
if (parent != null){
if (parent.key > node.key){
parent.left = node;
} else {
parent.right = node;
}
} else {
root = node;
}
//执行修复方法
insertFIxUp(node);
}
修复代码如下:
public void insertFIxUp(Node5 node){
Node5 parent = parentOf(node);
Node5 grandpa = parentOf(parent);
//父节点存在,并为红色,必有爷节点
if (parent != null && parent.color == true){
//如果在爷节点的左侧
if (grandpa.left == parent){
//叔节点
Node5 uncle = grandpa.right;
//叔叔节点存在,并为红,叔叔和父节点变黑,爷节点变红,已爷节点为插入节点进行修复
if (uncle != null && uncle.color == true){
uncle.color = BLACK;
parent.color = BLACK;
grandpa.color = RED;
insertFIxUp(grandpa);
//叔叔节点不存在,或为黑,
} else{
//插入节点为父节点的右子节点,父左旋,转化为LL
if (parent.right == node){
leftRotate(parent);
parent = node;
}
//插入节点为父节点的左节点,父爷改色,爷右旋
parent.color = BLACK;
grandpa.color = RED;
rightRotate(grandpa);
}
//如果在爷节点的右侧
} else {
//叔节点
Node5 uncle = grandpa.left;
//叔叔节点存在,并为红,叔叔和父节点变黑,爷节点变红,已爷节点为插入节点进行修复
if (uncle != null && uncle.color == true){
uncle.color = BLACK;
parent.color = BLACK;
grandpa.color = RED;
insertFIxUp(grandpa);
//叔叔节点不存在,或为黑,
} else{
//插入节点为父节点的左节点,父左旋,转化为RR
if (parent.left == node){
rightRotate(parent);
parent = node;
}
//插入节点为父节点的右节点,父爷改色,爷右旋
parent.color = BLACK;
grandpa.color = RED;
leftRotate(grandpa);
}
}
}
//设置root节点为黑色
root.setColor(BLACK);
}
总结:红黑树添加需要处理,父节点为红。根据叔叔节点情况处理
(二)、删除节点
删除红黑树节点,跟删除AVL树一致,删除节点后进行修复
删除节点 * 1.没有节点,直接删除 * 2.有一个节点,子节点和删除节点互换,删除子节点 * 3.有两个节点,找到后继节点或前驱节点,互换,删除后继节点 总结:删除的节点都没有子节点
代码如下
//删除
public void del(int key){
Node5 node = getKey(key);
if (node == null){
System.out.println("节点不存在");
}
if (node == root){
root = null;
} else {
//有两个节点,找到后继节点
if (node.left != null && node.right != null){
Node5 temp = delMix(node.right);
node.key = temp.key;
//删除节点变为后继节点
node = temp;
}
//有一个节点,或没有节点
Node5 temp = node.left == null ? node.right : node.left;
if (temp != null){
node.key = temp.key;
} else {
temp = node;
}
//修复红黑树,传入实际删除节点
deleteLeafFix(temp);
Node5 parent = parentOf(temp);
//删除父子节点关系
if (parent.left == temp) {
parent.left = null;
} else {
parent.right = null;
}
temp.parent = null;
}
}
修复方法及情况:
1.删除节点为红色,不需要处理
2.删除节点为黑色(重点)
删除节点为黑色时,兄弟节点为红色,我们需要进行处理,方便我们后续操作
原理:兄弟节点为红,其必有黑色子节点,父节点必为黑。
操作:父节点、兄弟节点互换色,父节点旋转,旋转之后,删除节点的兄弟节点变为,旋转之前的兄弟节点的子节点,为黑。处理完毕
以上可画图思考,其可以说是删除红黑树节点最优处理
1.兄弟节点有子节点(即旋转之后,可借黑)
根据兄弟节点和其子节点的关系分为:
1.RL、LR情况,兄弟节点、兄弟节点子节点换色,兄弟节点旋转,即可的到RR、LL
2.RR、LL情况,兄弟节点、父节点、兄弟节点右(左)节点变色,父节点左(右)旋
2.兄弟节点没有子节点(即旋转之后,不可借黑)
兄弟节点变红,从父节点开始循环(借黑)(此处查看大量红黑树教程,均有bug)
1.父节点有红,旋转红色节点,旋转之后的红色节点的父节点变黑(本身为黑)
2.父节点没有红,循环到root节点,root节点变红,旋转
代码如下:
private void deleteLeafFix(Node5 node) {
while (node != root && node.color == BLACK){
Node5 parent = parentOf(node); //父节点
//删除节点为父节点的左子节点
if (parent.left == node){
Node5 brother = parent.right;
//兄弟节点为红色,其父、子节点必为黑,把兄弟节点改黑(父兄互换色,父旋转)
if (brother.color == RED){
parent.color = RED;
brother.color = BLACK;
leftRotate(parent);
brother = parent.right;
}
//3.兄弟不借,兄弟没有子节点(向上递归,直到跟节点或者红色节点)
if (brother.left == null && brother.right == null){
brother.color = RED;
node = parent;
//循环到跟节点或者红色节点
while (node != root && node.color == BLACK){
node = node.parent;
}
//跟节点,变色,旋转
if (node == root){
node.color = RED;
leftRotate(node);
} else {
//有红色节点,旋转红色节点,旋转后红色节点的父节点变黑
leftRotate(node);
node.parent.color = BLACK;
node = root;
}
//2.兄弟有借,兄弟节点有子节点
} else {
//兄弟节点没有右节点,兄、兄左换色,兄右旋
if (brother.right == null){
brother.color = RED;
brother.left.color = BLACK;
rightRotate(brother);
brother = parent.right;
}
brother.color = parent.color;
parent.color = BLACK;
brother.right.color = BLACK;
leftRotate(parent);
node = root;
}
//删除节点为父节点的右子节点
}else {
Node5 brother = parent.left;
//兄弟节点为红色,其父、子节点必为黑,把兄弟节点改黑(父兄互换色,父旋转)
if (brother.color == RED){
parent.color = RED;
brother.color = BLACK;
rightRotate(parent);
brother = parent.left;
}
//3.兄弟无借,兄弟没有子节点
if (brother.left == null && brother.right == null){
brother.color = RED;
node = parent;
//循环到跟节点或者红色节点
while (node != root && node.color == BLACK){
node = node.parent;
}
//跟节点,变色,旋转
if (node == root){
node.color = RED;
rightRotate(node);
//红色节点,变黑色
} else {
//有红色节点,旋转红色节点,旋转后红色节点的父节点变黑
rightRotate(node);
node.parent.color = BLACK;
node = root;
}
//2.兄弟有借,兄弟节点有子节点
} else {
//兄弟节点没有右节点,兄、兄左换色,兄右旋
if (brother.left == null){
brother.color = RED;
brother.right.color = BLACK;
leftRotate(brother);
brother = parent.left;
}
brother.color = parent.color;
parent.color = BLACK;
brother.left.color = BLACK;
rightRotate(parent);
node = root;
}
}
}
//情况3,补充
root.color = BLACK;
}
以上即完成对红黑树的添加、删除。总的来说,红黑树理解它的原理及修复遇到的情况,手写红黑树,不算多难,与君共勉。
详情代码:suanfa/RedBlackTreeDemo.java at master · lovejiajia/suanfa · GitHub