前篇文章讲AVL树,如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树;但是由于AVL树在新增/减少结点的时候会进行旋转以保持AVL树的高度平衡,所以如果要对AVL树做一些结构修改的操作,性能非常低下,所以一个结构经常修改,就不太适合用AVL树来存储。
因此引入一个数据结构,也就是本文的重点——红黑树,红黑树通过维护结点的颜色来维持树的【相对平衡】,这里的相对平衡指的是红黑树的平衡没有AVL树那样严格,所以相对旋转的次数也会减少
满足下面条件就是红黑树:
因为有【对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点 】和【没有2个连续的红色节点】这两个性质就能推出【最长路径最多也就是最短路径的两倍】,因为其实最短路径就是一条路径上全是黑色的结点,最长路径就是一条路径上红黑交替的结点;假设最短路径上黑色结点为N个;那么最长路径由于是红黑交替,最短路径有N个黑色结点,那么最长路径也应该有N个黑色结点,加在上N个红色结点交替,所以最长路径最长也就2N
那么接下来我们来看一下红黑树节点的定义,首先要有一个枚举来表示颜色
public enum COLOR {
RED,BLACK
}
红黑树节点:
static class RBTreeNode {
public RBTreeNode left;
public RBTreeNode right;
public RBTreeNode parent;
public COLOR color;//结点颜色;
public int val;
public RBTreeNode(int val) {
this.val = val;
this.color = RED;
}
}
可以看到上面我顺便把构造方法提供了,那么可以看到为什么默认新的要插入的结点是颜色是红色呢,是随便写的吗?当然不是啊,接着往下看:
原因有两点:
public boolean insert(int val) {
RBTreeNode node = new RBTreeNode(val);
if(root == null) {
root = node;
root.color = COLOR.BLACK;
return true;
}
//寻找插入位置
RBTreeNode parent = null;
RBTreeNode cur = root;
while(cur!=null) {
if(cur.val<val) {
parent = cur;
cur = cur.right;
} else if (cur.val>val) {
parent = cur;
cur = cur.left;
} else {
return false;
}
}
//到这里cur = null
if(parent.val>val) {
parent.left = node;
} else {
parent.right = node;
}
node.parent = parent;
cur = node;
这几个变量后面会经常用,大家先记住
接下来进入正题,由于插入前这棵树本来就是红黑树,而我们新插入的结点默认是红色的,所以当新插入的结点的parent是黑色是,这时候是不会破坏红黑树的性质的,此时不用调整;只有当parent为红色结点的时候,我们才需要从cur结点开始往根节点向上调整【检查并调整树的颜色或结构】
所以外层的while循环的条件就出来了
while(parent!=null&&parent.color== RED)
那么既然只有当parent为红色时才会进入循环,所以parent不可能是根节点,因此grandparent一定存在
//parent是红色的,所以parent必有父亲节点
RBTreeNode grandparent = parent.parent;
既然granparent存在,那我们的大的情况就有两种parent是grandparent的左孩子和parent是grandparent的右孩子;而这两种情况里面又有三种小情况
我们先把代码总体框架给出
while(parent!=null&&parent.color== RED) {
//parent是红色的,所以parent必有父亲节点
RBTreeNode grandparent = parent.parent;
//情况1.0 parent是grandparent的左孩子
if(parent==grandparent.left) {
RBTreeNode uncle = grandparent.right;
//情况1.1 cur为红色,parent为红色 grandparent为黑色,uncle存在且为红色,
} else {
//情况1.3 cur为红,parent为红,grandparent为黑色,uncle不存在或者uncle为黑色,cur为parent右孩子
if(cur==parent.right) {
//然后就变成情况1.2
}
//情况1.2 cur为红,parent为红,grandparent为黑色,uncle不存在或者uncle为黑色,cur为parent左孩子
}
} else {
//情况2.0 parent是grandparent的右孩子 parent == grandparent.right
RBTreeNode uncle = grandparent.left;
//情况1.1 cur为红色,parent为红色 grandparent为黑色,uncle存在且为红色,
if(uncle!=null&&uncle.color== RED) {
;
} else {
//情况1.3 cur为红,parent为红,grandparent为黑色,uncle不存在或者uncle为黑色,cur为parent左孩子
if(cur == parent.left) {
}
//情况1.2 cur为红,parent为红,grandparent为黑色,uncle不存在或者uncle为黑色,cur为parent右孩子
}
}
}
//最后将根节点修改为黑色
root.color = COLOR.BLACK;
return true;
parent是grandparent的左孩子
cur为红,parent为红,grandparent为黑,uncle存在且为红
解决方法:将parent和uncle变成黑色,grandparent变成红色。
我相信把parent和uncle变成黑色大家看图后就能理解,当为什么要把grandparent变成红色呢?
因为grandparent有可能是有父亲节点的,而且grandparent的父亲节点有可能是黑的也可能是红的
如果grandparent的父亲节点是黑色的,就会造成各路径上的黑色结点数就不相等了,
所以我们把grandparent改成红色就可以解决这种情况
如果grandparent的父亲节点是红色的,那么此时就出现了两个红色结点,那该怎么办呢?
解决方法:让cur = grandparent,parent = grandparent.parent;然后再次进入循环,向上调整,看图:
那如果grandparent没有父亲结点,我们还把grandparent改成红色的,不就不满足红黑树了吗?解决这种方式也很简单,如果granparent没有父亲节点,那说明它就是根节点,此时就退出循环,把它修改成黑色就解决了
代码如下:
//情况1.1 cur为红色,parent为红色 grandparent为黑色,uncle存在且为红色,
if(uncle!=null&&uncle.color== RED) {
parent.color = COLOR.BLACK;
uncle.color = COLOR.BLACK;
grandparent.color = RED;//先变成红色,然后不是根节点,继续向上调整,如果为根节点
cur =grandparent;
parent = cur.parent;
}
cur为红,parent为红,uncle不存在或者为黑,cur是parent的左孩子
解决方式:对grandparent右旋,然后将parent改为黑色,将grandparent改为红
这种情况是在调整中的状态,因为你看下图,如果不是调整中才会出现的情况,它根本就不算是一颗红黑树(在插入cur之前,它也不是红黑树,因为黑色结点数量不对)
那么下图就是在调整中出现的情况1.2
由于左旋右旋前面一篇AVL树细节已经讲过了,这里就偷懒了放个链接:AVL树
代码如下:
//情况1.2 cur为红,parent为红,grandparent为黑色,uncle不存在或者uncle为黑色,cur为parent左孩子
//对grandparent右旋
rotateRight(grandparent);
//在修改颜色
grandparent.color = RED;
parent.color = COLOR.BLACK;
cur为红,parent为红,uncle不存在或者为黑,cur是parent的右孩子
看着好像跟情况1,2好像,但是你仔细看cur是parent的右孩子,这里不一样
解决方式:左旋,交换cur和parent的引用,就变成情况1.2了,然后用情况1.2的解决方法来进行调整,看图:
以1.2情况进行调整
到这里情况1.3也就讲完了
//情况1.3 cur为红,parent为红,grandparent为黑色,uncle不存在或者uncle为黑色,cur为parent右孩子
if(cur==parent.right) {
//先左旋
rotateLeft(parent);
//交换cur和parent的引用
RBTreeNode tmp = parent;
parent = cur;
cur = tmp;
//然后就变成情况1.2
}
//情况1.2 cur为红,parent为红,grandparent为黑色,uncle不存在或者uncle为黑色,cur为parent左孩子
//对grandparent右旋
rotateRight(grandparent);
//在修改颜色
grandparent.color = RED;
parent.color = COLOR.BLACK;
parent是grandparent的右孩子
情况2.0和情况1.0基本一样,就是改了个方向
cur为红,parent为红,grandparent为黑,uncle存在且为红
解决方法:将parent和uncle变成黑色,grandparent变成红色。
为什么把grandparent变成红色前面已经细讲了,这里就直接看图不再赘述
//情况1.1 cur为红色,parent为红色 grandparent为黑色,uncle存在且为红色,
if(uncle!=null&&uncle.color== RED) {
parent.color = COLOR.BLACK;
uncle.color = COLOR.BLACK;
grandparent.color = RED;
cur = grandparent;
parent = cur.parent;
}
cur为红,parent为红,uncle不存在或者为黑,cur是parent的右孩子
解决方式:对grandparent左旋,然后将parent改为黑色,将grandparent改为红
看图:
代码:
//情况2.2 cur为红,parent为红,grandparent为黑色,uncle不存在或者uncle为黑色,cur为parent右孩子
//对grandparent左旋
rotateLeft(grandparent);
//修改颜色
grandparent.color = RED;
parent.color = BLACK;
cur为红,parent为红,uncle不存在或者为黑,cur是parent的左孩子
解决方式:对parent进行右旋,交换parent和cur的引用,然后就变成情况2.2了,之后就以情况2.2处理即可
看图:
代码
//情况2.3 cur为红,parent为红,grandparent为黑色,uncle不存在或者uncle为黑色,cur为parent左孩子
if(cur == parent.left) {
//先右旋
rotateRight(parent);
//交换cur和parent的引用
RBTreeNode tmp = parent;
parent = cur;
cur = tmp;
}
//情况2.2 cur为红,parent为红,grandparent为黑色,uncle不存在或者uncle为黑色,cur为parent右孩子
//对grandparent左旋
rotateLeft(grandparent);
//修改颜色
grandparent.color = RED;
parent.color = BLACK;
package RBTree;
/**
* Created with IntelliJ IDEA.
* Description:
* User: ling
* Date: 2022-11-23
* Time: 19:34
*/
import AVLTree.AVLTree;
import com.sun.org.apache.regexp.internal.RE;
import static RBTree.COLOR.BLACK;
import static RBTree.COLOR.RED;
/**
* 红黑树实现
*/
public class RBTree {
static class RBTreeNode {
public RBTreeNode left;
public RBTreeNode right;
public RBTreeNode parent;
public COLOR color;//结点颜色;
public int val;
public RBTreeNode(int val) {
this.val = val;
this.color = RED;
}
}
public RBTreeNode root;
public boolean insert(int val) {
RBTreeNode node = new RBTreeNode(val);
if(root == null) {
root = node;
root.color = COLOR.BLACK;
return true;
}
//寻找插入位置
RBTreeNode parent = null;
RBTreeNode cur = root;
while(cur!=null) {
if(cur.val<val) {
parent = cur;
cur = cur.right;
} else if (cur.val>val) {
parent = cur;
cur = cur.left;
} else {
return false;
}
}
//到这里cur = null
if(parent.val>val) {
parent.left = node;
} else {
parent.right = node;
}
node.parent = parent;
cur = node;
//到这里【向上调整颜色】
//新插入的结点为红色的,如果父亲节点也是红色,就需要调整
while(parent!=null&&parent.color== RED) {
//parent是红色的,所以parent必有父亲节点
RBTreeNode grandparent = parent.parent;
//情况1.0 parent是grandparent的左孩子
if(parent==grandparent.left) {
RBTreeNode uncle = grandparent.right;
//情况1.1 cur为红色,parent为红色 grandparent为黑色,uncle存在且为红色,
if(uncle!=null&&uncle.color== RED) {
parent.color = COLOR.BLACK;
uncle.color = COLOR.BLACK;
grandparent.color = RED;//先变成红色,然后不是根节点,继续向上调整,如果为根节点
cur =grandparent;
parent = cur.parent;
} else {
//情况1.3 cur为红,parent为红,grandparent为黑色,uncle不存在或者uncle为黑色,cur为parent右孩子
if(cur==parent.right) {
//先左旋
rotateLeft(parent);
//交换cur和parent的引用
RBTreeNode tmp = parent;
parent = cur;
cur = tmp;
//然后就变成情况1.2
}
//情况1.2 cur为红,parent为红,grandparent为黑色,uncle不存在或者uncle为黑色,cur为parent左孩子
//对grandparent右旋
rotateRight(grandparent);
//在修改颜色
grandparent.color = RED;
parent.color = COLOR.BLACK;
}
} else {
//情况2.0 parent是grandparent的右孩子 parent == grandparent.right
RBTreeNode uncle = grandparent.left;
//情况2.1 cur为红色,parent为红色 grandparent为黑色,uncle存在且为红色,
if(uncle!=null&&uncle.color== RED) {
parent.color = COLOR.BLACK;
uncle.color = COLOR.BLACK;
grandparent.color = RED;
cur = grandparent;
parent = cur.parent;
} else {
//情况2.3 cur为红,parent为红,grandparent为黑色,uncle不存在或者uncle为黑色,cur为parent左孩子
if(cur == parent.left) {
//先右旋
rotateRight(parent);
//交换cur和parent的引用
RBTreeNode tmp = parent;
parent = cur;
cur = tmp;
}
//情况2.2 cur为红,parent为红,grandparent为黑色,uncle不存在或者uncle为黑色,cur为parent右孩子
//对grandparent左旋
rotateLeft(grandparent);
//修改颜色
grandparent.color = RED;
parent.color = BLACK;
}
}
}
//最后将根节点修改为黑色
root.color = COLOR.BLACK;
return true;
}
/**
* 左旋
* @param parent
*/
private void rotateLeft(RBTreeNode parent) {
RBTreeNode subR = parent.right;
RBTreeNode subRL = subR.left;
//记录一下parent的父亲节点
RBTreeNode pParent = parent.parent;
parent.right = subRL;
//检查有没有subRL
if(subRL!=null) {
subRL.parent = parent;
}
subR.left = parent;
parent.parent = subR;
//检查当前是不是根节点
if(parent==root) {
root = subR;
root.parent = null;
} else {
if(pParent.left == parent) {
pParent.left = subR;
} else {
pParent.right = subR;
}
subR.parent = pParent;
}
}
/**
* 右旋
* @param parent
*/
private void rotateRight(RBTreeNode parent) {
RBTreeNode subL = parent.left;
RBTreeNode subLR=subL.right;
//记录一下parent的父亲节点
RBTreeNode pParent = parent.parent;
parent.left = subLR;
//检查有没有subLR
if(subLR!=null) {
subLR.parent = parent;
}
subL.right = parent;
parent.parent = subL;
//检查当前是不是根节点
if(parent == root) {
root = subL;
root.parent = null;
} else {
//不是根节点,判断这棵子树是左子树还是右子树
if(pParent.left == parent) {
pParent.left = subL;
} else {
pParent.right = subL;
}
subL.parent = pParent;
}
}
}
检验一颗树是否为红黑树,只要检查满不满足它的性质即可
/**
* 检查一颗树是否为红黑树
* @return
*/
public boolean isRBTree() {
if(root == null) {
//如果一颗树是空树,那么这颗树就是红黑树
return true;
}
if(root.color!=BLACK) {
System.out.println("违反了性质;根节点必须是黑色的");
}
//计算最左边路径上的黑色结点个数
int blackNum = 0;
RBTreeNode cur = root;
while(cur!=null) {
if(cur.color==BLACK) {
blackNum++;
}
cur = cur.left;
}
// 检查有没有连续出现两个红色的结点&&检查每条路径上的黑色结点是否相同
return checkRedColor(root)&&checkBlackNum(root,0,blackNum);
}
/**
* 检查有没有连续出现两个红色的结点
* @param root
* @return
*/
private boolean checkRedColor(RBTreeNode root) {
if(root == null) {
return true;
}
if(root.color == RED) {
RBTreeNode parent = root.parent;
if(parent.color==RED) {
System.out.println("违反了性质,连续出现了两个红色的结点");
return false;
}
}
return checkRedColor(root.left)&&checkRedColor(root.right);
}
/**
* 检查每条路径上的黑色结点是否相同
* pathBlackNum:每次递归是,计算黑色节点的个数
* blackNum:事先计算好的一条路径上的黑色结点
* @param root
* @param pathBlackNum
* @param blackNum
* @return
*/
private boolean checkBlackNum(RBTreeNode root,int pathBlackNum,int blackNum) {
if(root==null) {
return true;
}
if(root.color==BLACK) {
pathBlackNum++;
}
if(root.left==null&&root.right==null) {
if(pathBlackNum!=blackNum) {
System.out.println("违反了性质,某条路径上黑色的结点个数不一样");
return false;
}
}
return checkBlackNum(root.left,pathBlackNum,blackNum)&&checkBlackNum(root.right,pathBlackNum,blackNum);
}
这里提供一个测试用例
int[] array = {4,2,6,1,3,5,15,7,16,14};
RBTree rbTree = new RBTree();
for(int i=0;i<array.length;i++) {
rbTree.insert(array[i]);
}
System.out.println(rbTree.isRBTree());
rbTree.inorder(rbTree.root);