红黑树的概念
- 红黑树是一种含有红黑节点并且能够自平衡的二叉查找树
红黑树的五大性质(重点):
1、 每个节点要么是黑色,要么是红色;
2、 根节点是黑色;
3、 每个叶子节点都是(NIL)都是黑色的(都是null,虚拟出来的);
4、 每个红色节点的两个子节点一定是黑色的;
5、任意一节点到每个叶子节点的路径都包含数量相同的黑节点(黑色完美平衡)。
红黑树的添加:
1、 红黑树的添加分为两个步骤:
- 第一,因为红黑树本身也是二叉查找树,所以我们每次插入节点首先按二叉查找树的插入方法进行插入;
- 第二,因为为了满足红黑树的性质,每次插入后,都需要进行修正,使它满足红黑树的五大性质。
2、红黑树的插入节点我们都是默认用红色节点插入,原因如下:
-
首先,我们看一下红黑树的五大性质,当我们插入的是红色节点时,
1)性质1:不会违背
2)性质2:如果根节点不存在,只需要把插入节点作为根节点,把颜色变为黑色即可,操作成本低;
3)性质3:叶子节点是虚拟出来的null节点,插入的是红色对性质3没有影响;
4)性质4:这个有可能会违背
5)性质5:因为我们插入的是红色,不会影响性质5黑色节点的数量,不会违背。 -
总结:从上述来说,插入的节点是红色不会影响性质5,我们在进行修正的时候就不需要考虑多一条性质,这样操作就会比较简单。
3、上面讲了,添加的步骤总体分为两步:按二叉查找树的性质添加+修正。
3.1.二叉查找树的添加相信大家都会,等下只在代码中实现。
3.2.我主要来讲修正的过程.
- 首先,我需要先声明一下,我下面用的一些单词的含义:
-
红黑树自平衡的最小单元
概念:就是每次操作自平衡时需要操作的节点 -
修正的过程如下图:
3、 上面的情况很容易记忆,接下来详细解释上面四种情况。
1)当红黑树为空时,插入的节点就作为根节点
- 情况1:需要把颜色变为黑色,满足红黑树性质2;
2)当红黑树不为空时,则需要判断当前节点C的父节点P的颜色
- 情况2:如果父节点P的颜色为黑色,则可以直接插入,不会违背红黑树的性质;
2.1)如果父节点P的颜色为红色,则违背了性质4,这时候就需要进行修正了,这时需要判断当前节点的叔叔节点U的颜色
- 情况3:当叔叔节点为红色时,这时需要将父节点P和叔叔节点U都变为黑色,祖父节点G变为红色,因为祖父节点G的颜色变为红色,这时,需要把祖父节点G作为新的当前节点安装情况1,2,3,4进行递归处理;
- 情况4:当叔叔节点为空(默认黑色),或者叔叔节点颜色为黑色时,需要分成下面两种情况:
- 情况4.1:CPG三点一线(当前节点,父节点,祖父节点在一条直线上,如果不懂,看下面图)
处理:(左旋)以P为圆心,旋转G点
//左旋的函数,参数是祖父节点G
private void leftRotation(RBTNode<T> G){
RBTNode<T> P = G.right; //得到父节点P
//当G为根节点时
if(G==mRoot){
G.right = P.left;
P.left = G;
mRoot = P;
}
//当G不为根节点时,分为两种情况
//情况1:G是G的父节点的左孩子,
//情况2:G是G的父节点的右孩子,
else {
G.right = P.left;
P.left = G;
//情况1
if(G==G.parent.left){
G.parent.left = P;
}
//情况2
else {
G.parent.right = P;
}
P.parent=G.parent;
G.parent = P;
}
//变色操作
boolean tempColor = G.color;
G.color = P.color;
P.color = tempColor;
}
总结情况4.1:我上面只是列举了左旋的情况,右旋同理,【代码见】
- 情况4.2:CPG三角关系(看图)
处理:**需要把它编程直线关系,再按情况4.1处理,以C为圆心,旋转P点,
结果如图:
总结情况4.2:上面就是展现把三角关系变成直线关系,再进行左旋,或者右旋就可以再次变成一个二叉树了。
总结情况1,2,3,4:上面4种情况涵盖了红黑树的所有添加操作。
代码实现:
import org.junit.Test;
/**
*java语言红黑树添加操作
* @param <T>
*/
public class RBTree<T extends Comparable<T>> {
/**
* 一个红黑树必须要有根节点
*/
private RBTNode<T> mRoot;
//设置两个全局颜色变量,false代表红色,true代表黑色
private static final boolean RED = false;
private static final boolean BLACK = true;
//RBTree的构造方法,根节点初始值为空
public RBTree() {
this.mRoot = null;
}
/**
* 1.红黑树必须要有节点,首先定义一个节点类(内部类)
* 1.1.节点必须包括下面几个属性
* 节点的颜色
* 节点的值
* 节点的父节点
* 节点的左子节点
* 节点的右子节点
*/
public class RBTNode<T extends Comparable<T>>{
boolean color; //颜色
T key; //节点值
RBTNode<T> left; //左子节点
RBTNode<T> right; //右子节点
RBTNode<T> parent; //父节点
//带参构造方法
public RBTNode(boolean color, T key, RBTNode<T> left, RBTNode<T> right, RBTNode<T> parent) {
this.color = color;
this.key = key;
this.left = left;
this.right = right;
this.parent = parent;
}
//获取节点的值
public T getKey() {
return key;
}
//设置遍历的时候输出的内容,值+颜色
@Override
public String toString() {
return "RBTNode{" +
"key=" + key +
" color=" + (this.color==RED?"R":"B")+
'}';
}
}
/**
* 2.我们要进行添加,因为红黑树也是二叉查找树,所以:
* 2.1添加的时候先按二叉查找树的方法添加,
* 2.2添加完再修正
*/
/**
* 2.1
* 1)插入时,先找到我们要插入的节点的父节点,
* 2)如果父节点为空,则表明该树还没有插值,就把值付给根节点
* 如果父节点不为空,则比较大小判断是插在父节点的左子树位置还是右子树位置。
* @param node //插入的节点
*/
private void insert(RBTNode<T> node){
int cmp;
RBTNode<T> y = null; //找到插入节点的父节点
RBTNode<T> x = mRoot; //根节点
//当根节点不为空时,找到插入节点的父节点
while(x != null){
y = x;
cmp = node.key.compareTo(x.key);
if(cmp<0){
x=x.left;
}
else {
x=x.right;
}
}
node.parent = y;
//找到父节点后,判断大小将节点添加进去
if(y!=null){
cmp = node.key.compareTo(y.key);
if(cmp<0){
y.left = node;
}
else {
y.right = node;
}
}
//当根节点为空时,直接将该节点作为根节点
else{
mRoot = node;
}
insertFixUp(node);
}
//测试方法调用,提供一个T key,调用insert(RBTNode<T> node)插入节点
public void insert(T key){
//将插入的节点设置为红色,(红黑树默认插入节点为红色,只违反一条规则)
RBTNode<T> node = new RBTNode<T>(RED,key,null,null,null);
if(node !=null){
insert(node);
}
}
/**
* 右旋
* @Param G 祖父节点
*/
private void rightRotation(RBTNode<T> G){
RBTNode<T> P = G.left;
//当祖父节点为根节点时
if(G==mRoot){
G.left = P.right;
P.right = G;
mRoot = P;
}
//当祖父节点不为根节点时
else{
G.left = P.right;
P.right = G;
if(G==G.parent.left){
G.parent.left = P;
}
else {
G.parent.right = P;
}
P.parent=G.parent;
G.parent = P;
}
//变色操作
boolean tempColor = G.color;
G.color = P.color;
P.color = tempColor;
}
/**
* 左旋
*/
private void leftRotation(RBTNode<T> G){
RBTNode<T> P = G.right;
if(G==mRoot){
G.right = P.left;
P.left = G;
mRoot = P;
}
else {
G.right = P.left;
P.left = G;
if(G==G.parent.left){
G.parent.left = P;
}
else {
G.parent.right = P;
}
P.parent=G.parent;
G.parent = P;
}
//变色操作
boolean tempColor = G.color;
G.color = P.color;
P.color = tempColor;
}
/**
* 2.2红黑树插入的修正函数(变色,自旋,自平衡)
* 1)红黑树自平衡的最小单元是GPC
* G:GrandFather 祖父节点
* P:Parent 父节点
* C:Current 当前插入的节点
* 2)定义这三个节点:
* 1)@Param node 当前节点
* 2)自己定义 G 祖父节点
* 3)自己定义 P 父节点
* 4)自己定义 U 叔叔节点 Uncle
*/
private void insertFixUp(RBTNode<T> C){
RBTNode<T> G = null; //祖父节点
RBTNode<T> P; //父节点
RBTNode<T> U = null; //叔叔节点
//得到插入节点的父节点
P = C.parent!=null?C.parent:null;
//得到插入节点的祖父节点
if(P!=null){
G = P.parent!=null?P.parent:null;
}
//得到插入节点的叔叔节点
if(G!=null){
U = G.left==P?G.right:G.left;
}
//情况1
if(C==mRoot){
mRoot.color = BLACK;
}
//情况2
else if(C.parent.color==BLACK){
//不用操作
}
//情况3
else if(U!=null&&P.color==RED && U.color==RED){
G.color = RED;
P.color = BLACK;
U.color = BLACK;
//因为G节点变为红色了,所以需要把G节点作为新的插入节点递归调整
insertFixUp(G);
}
//情况4
else{
//父节点为祖父节点的左孩子
if(P == G.left){
//当前节点为父节点的左孩子,CPG三点一线的情况
if(C == P.left){
//右旋
rightRotation(G);
}
//CPG三角关系
else{
//先变为三点一线
G.left = C;
C.parent=G;
C.left = P;
P.parent = C;
P.right = null;
//再右旋
rightRotation(G);
}
}
//父节点为祖父节点的右孩子
else{
//当前节点为父节点的右孩子,CPG三点一线的情况
if(C == P.right){
//左旋
leftRotation(G);
}
//CPG三角关系
else {
//先变三角为直线
G.right = C;
C.parent = G;
C.right = P;
P.parent = C;
P.left = null;
//再左旋
leftRotation(G);
}
}
}
}
/**
* 中序遍历
*/
private void inOrder(RBTNode<T> tree){
if(tree!=null){
inOrder(tree.left);
System.out.print(tree.key+" ");
inOrder(tree.right);
}
}
public void inOrder(){
inOrder(this.mRoot);
}
/**
* 前序遍历
*/
private void preOrder(RBTNode<T> tree){
if(tree!=null){
System.out.print(tree.key+" ");
preOrder(tree.left);
preOrder(tree.right);
}
}
public void preOrder(){
preOrder(this.mRoot);
}
/**
* 后序遍历
*/
private void postOrder(RBTNode<T> tree){
if(tree!=null){
postOrder(tree.left);
postOrder(tree.right);
System.out.print(tree);
}
}
public void postOrder(){
postOrder(this.mRoot);
}
/**
* 通过键值查找节点
*/
private RBTNode<T> getByKey(T key){
int cmp;
RBTNode<T> x = mRoot;
while (x!=null){
cmp = x.key.compareTo(key);
if(cmp<0){
x= x.right;
}
else if(cmp>0) {
x=x.left;
}
else {
return x;
}
}
return null;
}
@Test
public void test1(){
RBTree<Integer> trbTree = new RBTree<>();
trbTree.insert(60);
trbTree.insert(70);
trbTree.insert(80);
trbTree.insert(90);
trbTree.insert(100);
trbTree.insert(140);
trbTree.insert(130);
trbTree.insert(110);
trbTree.insert(105);
trbTree.insert(120);
//得到某个节点的情况
System.out.println(trbTree.getByKey(130).parent);
System.out.println(trbTree.getByKey(130).left);
System.out.println(trbTree.getByKey(130).right);
// trbTree.insert(100);
// trbTree.insert(90);
// trbTree.insert(80);
// trbTree.insert(70);
// trbTree.insert(60);
// trbTree.insert(65);
// trbTree.insert(62);
trbTree.inOrder();
System.out.println();
trbTree.preOrder();
System.out.println();
//后续遍历输出了颜色,其他两个我只是输出Key
trbTree.postOrder();
}
}