红黑树
在二叉搜索树的基础上给节点增加了一个颜色属性,通过该属性的控制来实现平衡。
性质:
1、结点不是红色就是黑色。
2、根结点一定为黑色。
3、所有的NULL指针被认为是黑色结点。
(在红黑树中,叶子节点指的不是度为0的节点,而是度为0的节点的两个null孩子)
4、没有两个连着的红结点。
5、从任一节点到其每个叶子的所有简单路径上的黑色结点数都相同。
与AVL树相比,红黑树是一种弱平衡的搜索二叉树:
AVL是保证了任意一个节点的平衡因子在[-1,0,1]之间,平衡性极高;
而红黑树的性质4保证了树中任两条路径长度相差不超过一倍;
因此ACL树在每次插入删除后一旦平衡因子绝对值大于1就要旋转,其插入、删除相对于红黑树而言效率较低;
相反的,因为平衡度高,所以AVL树查找效率也就更快。
但红黑树的平衡性也能保证它的最低查找效率为 logn。
由此可见,AVL树维护高度平衡而付出的代价是比较高的;红黑树的适当取舍使得其应用更广泛。
如:
- 广泛用于C ++的STL中,地图是用红黑树实现的;
- Linux的的进程调度,用红黑树管理进程控制块,进程的虚拟内存空间都存储在一颗红黑树上,每个虚拟内存空间都对应红黑树的一个节点,左指针指向相邻的虚拟内存空间,右指针指向相邻的高地址虚拟内存空间;
- IO多路复用的epoll采用红黑树组织管理sockfd,以支持快速的增删改查;
- Nginx中用红黑树管理定时器,因为红黑树是有序的,可以很快的得到距离当前最小的定时器;
- Java的TreeMap的实现;
- jdk1.8之后的HashMap超过一定长度时使用。
红黑树的节点共有五个域:
key、data、lchild、rchild、parent
因为加入了parent指针,所以每次改变关系时一定要记得父子都要重新设置相应指针。
贴一篇不错的文章:关于插入节点的
红黑树的插入与普通的BST一致,插入后的平衡控制也比较简单,分为四种情况,大概是:
(设插入节点为node,根节点为root,双亲为parent,祖父grand, uncle叔叔)
1.root为空,node即为root,上黑色即可;
2.node的 parent为黑,啥也不用干;
3.node 的 parent 为红且uncle 也为红:
因为节点默认为红,即node 为红,所以此时违反了性质4;
uncle 是红色的,这时可以把 parent跟uncle 同时设为黑色,把 grand 设为红色,实现子树的平衡,然后再自底向上进
行调节(对 grand递归),直接爸爸为黑或者遇到根节点为止;
4.node 的 parent 为红而uncle 为黑:
现在不能一起变黑去拆两红啦呜呜呜,既然如此那我就送你一个红节点好了…!
[先看 node 自己在哪里 ,如果是在左右(也就是身为爷爷的左儿子的右儿子) / 右左,那就要先把自己旋转到parent的
位置;]
然后再 parent 跟 grand 换色 、把变成了红色的 grand 给转到另一边去吧~hhh
删除操作就有点难搞啦…:
跟BST删除一样分成三种基本情况,根据不同的情况进行平衡
贴几篇不错的文章:
1 、被删节点有两个子节点时:
先把其直接前驱 / 直接后继的值赋给目标节点 ,再去删直接前驱 / 后继(递归删除),转换成情况 2 或 3 ;
2 、被删结点有一个子节点时 ,
根据性质5 可知 ,这个子节点一定是红色的 ;根据性质4 可知 ,目标节点一定黑色的 。
所以,所以!直接把目标节点删掉,让子节点顶替它的位置跟颜色就好啦~
(记得判断目标节点会不会是根节点呢~)
3、被删结点没有子节点时,(此处以被删结点位于parent左方为例)
3-1:被删结点为红色 / 根节点,直接删掉,(就是把 parent 的 child 置空),结束!
3-2:被删结点为黑色
3-2-1: 被删结点的 brother为红色时 ,
-
此时brother一定有两个黑色子节点。先把 brother 跟 parent 换色 ,然后左旋让 brother 变成子树的根,这时就让 被删结点的brother变成黑色了,这样就变成了 3-2-2 的情况了; 建议用 if 程序段进行判断,就不用麻烦递归啦 ;
3-2-2:被删结点的 brother为黑色,
-
3-2-2-1:如果brother的两个子节点为空或黑(可能为黑是因为有可能是被向上平衡的),
-
3-2-2-1-1:如果parent为红色, 直接把brother跟parent换色,结束!
-
3-2-2-2-2:如果parent为黑色, 这下右边救不了左边了,只能把被删结点的替代置为parent,往上去平衡; - 3-2-2-2:如果brother的左子节点为红(有得利用啦) 把左子节点变成parent的颜色,把parent变成黑色;让brother右旋,让parent左旋; 这样就给左边送了一个黑色节点,左子节点被拿去替代parent啦; !!!要注意所有旋转操作都要判断会不会影响到树的根节点!!!
-
3-2-2-3:如果brother的右子节点为红 把brother变成parent颜色 ,把parent变成黑色,parent左旋 ,同样是给左边送了个黑节 点; 再把右子节点变成黑色 ,弥补右边被拿去替代parent的brother黑节点。
终于到了最刺激的时候了!撸代码!
先定义节点类:写入一个找叔叔的方法,插入的时候用。
public class RBNode<T extends Comparable<? super T>, D> {
private static final boolean RED = false;
private static final boolean BLACK = true;
T key;
D data;
boolean color;
RBNode<T, D> parent;
RBNode<T, D> lchild;
RBNode<T, D> rchild;
public RBNode(T key, D data) {
this.key = key;
this.data = data;
this.color = RED;
}
public RBNode(T key, D data, RBNode<T, D> parent) {
this(key, data);
this.parent = parent;
}
public RBNode(T key, D data, RBNode<T, D> parent, RBNode<T, D> lchild, RBNode<T, D> rchild) {
this(key, data, parent);
this.lchild = lchild;
this.rchild = rchild;
}
//获取某节点在以root为根节点的树中的叔叔节点
public RBNode<T, D> getUncle(RBNode<T, D> root, RBNode<T, D> node) {
if(null == root || null == node)
return null;
RBNode<T, D> parent = node.parent;
RBNode<T, D> grandparent = parent.parent;
if(null != grandparent) {
if(grandparent.lchild == parent) //如果爸爸是爷爷的左儿子,就返回爷爷的右孩子
return grandparent.rchild;
return grandparent.lchild;
}
return null; //如果爷爷不存在
}
}
红黑树类,定义了CRUD方法(也就几百行嘻嘻)
public class RBTree<T extends Comparable<? super T>, D> {
private static final boolean RED = false;
private static final boolean BLACK = true;
public RBNode<T, D> root;
public RBTree() {}
public RBTree(RBNode<T, D> root) {
this.root = root;
root.color = BLACK;
}
//把节点插入到合适位置
private boolean insertToTree(RBNode<T, D> root, RBNode<T, D> node) {
if(null == node)
return false;
if(null == root) {
this.root = node; //此处设的是对象的root,不是传进来的参数
return true;
}
int com = node.key.compareTo(root.key);
if(com < 0) { //key小于根节点时
if(null == root.lchild) {
root.lchild = node;
node.parent = root;
return true;
}
return insertToTree(root.lchild, node);
}else if(com > 0){ //key大于根节点时
if(null == root.rchild) {
root.rchild = node;
node.parent = root;
return true;
}
return insertToTree(root.rchild, node);
}else { // 已存在该key时
System.out.println(node.key + "为不合法key!");
return false;
}
}
//插入并平衡
public boolean insertAndBalance(RBNode<T, D> root, RBNode<T, D> node) {
boolean result = insertToTree(root, node);
if(result) {
balanceOfInsert(this.root, node); //插入后根节点可能被改变了
return true;
}
return false;
}
//平衡插入的结点,共有四种情况
//传入的root必须为整棵树的根节点,因为平衡的旋转可能涉及到根节点的改变
//平衡的旋转可能涉及到根节点的改变,要判断
private void balanceOfInsert(RBNode<T, D> root, RBNode<T, D> node) {
if(root == node) { //1、插入节点为根节点
root.color = BLACK; //把根节点设为黑色(新建节点默认为红)
return;
}
if(node.parent.color == BLACK) { //2、新节点的爸爸为黑色,不用调节
return;
}
while(node.parent.color != BLACK && node != root) {
//3、如果爸爸跟叔叔都是红色
if(null != node.getUncle(root, node) && node.getUncle(root, node).color == RED) {
node.parent.color = BLACK;
node.getUncle(root, node).color = BLACK;
node.color = RED;
node.parent.parent.color = RED;
node = node.parent.parent;
if(node == root) { //如果已到根节点要重新恢复黑色
root.color = BLACK;
break;
}
}else { //4、如果爸爸为红、叔叔为黑或者不存在
//判断旋转的是不是根节点,若是转完要重设this.root
//旋转还要换色,把目标旋转结点(node.parent.parent)设为RED,新"根"节点设为BLACK
boolean isRoot = node.parent.parent == root?true:false;
if(node.parent == node.parent.parent.lchild) {
if(node == node.parent.lchild) { //4.1 左左: 插入节点为左儿子且其爸爸也为左儿子
node.parent.parent.color = RED;
RBNode<T, D> newRoot = rotateRight(node.parent.parent);
newRoot.color = BLACK;
if(isRoot) this.root = newRoot;
break;
}else { //4.2 左右
node.parent.parent.color = RED;
RBNode<T, D> newRoot = rotateLeft(node.parent);
RBNode<T, D> newRoot2 = rotateRight(newRoot.parent);
newRoot2.color = BLACK;
if(isRoot) this.root = newRoot2;
break;
}
}else {
if(node == node.parent.rchild) { //4.1 右右:
node.parent.parent.color = RED;
RBNode<T, D> newRoot = rotateLeft(node.parent.parent);
newRoot.color = BLACK;
if(isRoot) this.root = newRoot;
break;
}else { //4.2 右左
node.parent.parent.color = RED;
RBNode<T, D> newRoot = rotateRight(node.parent);
RBNode<T, D> newRoot2 = rotateLeft(newRoot.parent);
newRoot2.color = BLACK;
if(isRoot) this.root = newRoot2;
break;
}
}
}
}
}
//计算某个节点到其任两个叶子节点路径上的黑色节点数
public int[] calculateBlackNode(RBNode<T, D> node) {
int[] num = new int[2];
num[0] = 0;
num[1] = 0;
if(null == node) {
return null;
}
RBNode<T, D> node1 = node;
while(null != node) {
if(null != node.lchild && node.lchild.color == BLACK) {
num[0]+=1;
}
node = node.lchild;
}
while(null != node1) {
if(null != node1.rchild && node1.rchild.color == BLACK) {
num[1]+=1;
}
node1 = node1.rchild;
}
return num;
}
//左旋算法
//包括儿子转让、原新节点的关系重设、双亲转让
//注意要设lchild / rchild以及parent
public RBNode<T, D> rotateLeft(RBNode<T, D> root){
if(null == root || null == root.rchild)
return null;
RBNode<T, D> newRoot = root.rchild;
RBNode<T, D> remain = null == newRoot.lchild ? null:newRoot.lchild;
root.rchild = remain; //转让儿子
if(null != remain) {
remain.parent = root; //若有,也要给儿子重设双亲
}
newRoot.parent = root.parent; //把原节点的双亲拿过来!注意此处不一定是旋转整棵树根节点,所以parent不能直接设为null
if(null != root.parent) //同样,如果不为空,还要重设双亲的儿子
if(root == root.parent.lchild) {
root.parent.lchild = newRoot;
}else {
root.parent.rchild = newRoot;
}
newRoot.lchild = root; //重设原、新节点关系
root.parent = newRoot; //重设原、新节点关系
return newRoot;
}
//右旋算法
public RBNode<T, D> rotateRight(RBNode<T, D> root){
if(null == root || null == root.lchild)
return null;
RBNode<T, D> newRoot = root.lchild;
RBNode<T, D> remain = null == newRoot.rchild ? null:newRoot.rchild;
root.lchild = remain; //转让儿子,把新根节点的左儿子接到源根节点上
if(null != remain)
remain.parent = root;
newRoot.parent = root.parent; //转让双亲(要在重设父子关系之前,才能找到原爸爸)
if(null != root.parent)
if(root == root.parent.lchild) {
root.parent.lchild = newRoot;
}else {
root.parent.rchild = newRoot;
}
root.parent = newRoot; //重设关系
newRoot.rchild = root;
return newRoot;
}
//中序遍历
public void midTraversal(RBNode<T, D> root) {
if(null == root)
return;
midTraversal(root.lchild);
System.out.println(root.key + ":" + root.color);
midTraversal(root.rchild);
}
//前序遍历
public void beforeTraversal(RBNode<T, D> root) {
if(null == root)
return;
System.out.println(root.key + ":" + root.color);
beforeTraversal(root.lchild);
beforeTraversal(root.rchild);
}
//后序遍历
public void afterTraversal(RBNode<T, D> root) {
if(null == root)
return;
afterTraversal(root.lchild);
afterTraversal(root.rchild);
System.out.println(root.key + ":" + root.color);
}
//删除节点
//三种情况,前两种直接转化 or 平衡即可
public void deleteAndBalance(RBNode<T, D> node) {
//1、有两个子节点,递归转化
if(null != node.lchild && null != node.rchild) {
RBNode<T, D> successor = node.rchild;
while(null != successor.lchild) { //寻找直接后继
successor = successor.lchild;
}
node.key = successor.key;
node.data = successor.data;
deleteAndBalance(successor);
}else if(null != node.lchild && null == node.rchild ||
null != node.rchild && null == node.lchild ){ //2、只有一个子节点
if(null != node.lchild) { //2.1 子节点在左边
if(node == this.root) { // 若删除节点是根节点
this.root = node.lchild;
this.root.color = BLACK;
}else if(null == node.parent){ //防止传进来的节点有误(既不是根节点又无parent
System.out.println("invaluable node!");
}else {
node.lchild.color = BLACK;
if(node == node.parent.lchild) {
node.parent.lchild = node.lchild;
}else {
node.parent.rchild = node.lchild;
}
}
}else { //2.2 子节点在右边
if(node == this.root) {
this.root = node.rchild;
this.root.color = BLACK;
}else if(null == node.parent){
System.out.println("invaluable node!");
}else {
node.rchild.color = BLACK;
if(node == node.parent.lchild) {
node.parent.lchild = node.rchild;
}else {
node.parent.rchild = node.rchild;
}
}
}
}else { //3、无子节点时
if(node == this.root) { //删除节点为根节点,直接置空
this.root = null;
}else if(null == node.parent){
System.out.println("invaluable node!");
}else {
if(node == node.parent.lchild) {
if(node.color == RED) { //删除节点为RED,直接删除
node.parent.lchild = null;
}else {
balanceOfDelete(node, node.parent); //为黑色需要平衡
node.parent.lchild = null;
}
}else {
if(node.color == RED) { //删除节点为RED,直接删除
node.parent.rchild = null;
}else {
balanceOfDelete(node, node.parent); //为黑色需要平衡
node.parent.rchild = null;
}
}
}
}
}
//删除前的平衡
private void balanceOfDelete(RBNode<T, D> node, RBNode<T, D> parent) {
while(node.color == BLACK && node != this.root){
if(node == parent.lchild){ // 1.删除节点在parent左边
RBNode<T, D> brother = parent.rchild;
if(brother.color == RED) {
parent.color = RED; //交换parent跟brother的颜色
brother.color = BLACK;
RBNode<T, D> result = rotateLeft(parent);
if(parent == this.root) {this.root = result;} //转了根节点要换根
brother = parent.rchild; //转化成brother为黑色的情况进行处理
}
//经过转换,接下来的brother都是黑色的了
//此处因为有可能是经过向上平衡的,所以brother可能有黑色子节点
if((brother.lchild == null || brother.lchild.color == BLACK)
&& (brother.rchild == null || brother.rchild.color == BLACK)) {
if(parent.color == RED) { //1.1 brother没有子节点且parent为RED
parent.color = BLACK;
brother.color = RED; //直接利用parent就可以实现平衡
break;
}else { //1.2 brother没有子节点且parent为黑
brother.color = RED;
node = parent; //实现小树的平衡,再向上平衡
parent = node.parent;
}
}else { //brother子节点不全为空的情况
if(null != brother.lchild) { //1.3 brother左子节点不为空,(一定为RED
brother.lchild.color = parent.color;
parent.color = BLACK;
rotateRight(brother);
RBNode<T, D> result = rotateLeft(parent);
if(parent == this.root) {this.root = result;}
}else{ //1.4 brother右子节点不为空,(一定为RED
brother.color = parent.color;
parent.color = BLACK;
brother.rchild.color = BLACK; //多一步,把拿去当parent的黑色节点平回来
RBNode<T, D> result = rotateLeft(parent);
if(parent == this.root) {this.root = result;}
}
break;
}
}else { //2、删除节点在parent右边
RBNode<T, D> brother = parent.lchild;
if(brother.color == RED) {
brother.color = BLACK;
parent.color = RED;
RBNode<T, D> result = rotateRight(parent);
if(parent == this.root) {this.root = result;}
brother = parent.lchild;
}
if((brother.lchild == null || brother.lchild.color == BLACK)
&& (brother.rchild == null || brother.rchild.color == BLACK)) {
if(parent.color == RED) {
parent.color = BLACK;
brother.color = RED;
break;
}else {
brother.color = RED;
node = node.parent;
parent = node.parent;
}
}else {
if(null != brother.lchild) {
brother.color = parent.color;
parent.color = BLACK;
brother.lchild.color = BLACK;
RBNode<T, D> result = rotateRight(parent);
if(parent == this.root) {this.root = result;}
}else {
brother.rchild.color = parent.color;
parent.color = BLACK;
rotateLeft(brother);
RBNode<T, D> result = rotateRight(parent);
if(parent == this.root) {this.root = result;}
}
break;
}
}
}
}
//查找某个key的所在的节点
public RBNode<T, D> searchNode(RBNode<T, D> root, T key) {
if(null == root)
return null;
if(key == root.key)
return root;
else if(key.compareTo(root.key)<0)
return searchNode(root.lchild, key);
else
return searchNode(root.rchild, key);
}
//查找某个key的data
public D search(RBNode<T, D> root, T key) {
RBNode<T, D> node = searchNode(root, key);
return node.data;
}
//更新某个key的data
public void update(RBNode<T, D> root, T key, D data) {
RBNode<T, D> node = searchNode(root, key);
node.data = data;
}
}
测试类
public class test {
public static void main(String[] args) {
//不可为基本数据类型,因RBNode的key可为null,基本数据类型不可以
RBNode<Integer,String> root = new RBNode<Integer, String>(2,"key为2");
RBTree<Integer, String> t1 = new RBTree<Integer, String>(root);
RBNode<Integer,String> node1 = new RBNode<Integer, String>(0,"key为0");
RBNode<Integer,String> node2 = new RBNode<Integer, String>(1,"key为1");
RBNode<Integer,String> node3 = new RBNode<Integer, String>(4,"key为4");
RBNode<Integer,String> node4 = new RBNode<Integer, String>(3,"key为3");
// RBNode<Integer,String> node5 = new RBNode<Integer, String>(17,"key为17");
t1.insertAndBalance(t1.root, node1);
t1.insertAndBalance(t1.root, node2);
t1.insertAndBalance(t1.root, node3);
t1.insertAndBalance(t1.root, node4);
// t1.insertAndBalance(t1.root, node5);
for(int i=0; i<18; i++) {
RBNode<Integer,String> node = new RBNode<Integer, String>(i,"key为"+i);
t1.insertAndBalance(t1.root, node);
}
t1.deleteAndBalance(node3);
t1.midTraversal(t1.root);
System.out.println("-------");
t1.beforeTraversal(t1.root);
int[] num = t1.calculateBlackNode(t1.root);
System.out.println("-------");
System.out.println(num[0] +","+ num[1]);
System.out.println("-------");
System.out.println(t1.search(t1.root, 1));
System.out.println("-------");
t1.update(t1.root, 1, "我是宝贝");
System.out.println(t1.search(t1.root, 1));
}
}
欢迎指出问题、共同进步!