再见!红黑树!(八大数据结构

红黑树

在二叉搜索树的基础上给节点增加了一个颜色属性,通过该属性的控制来实现平衡。

性质

1、结点不是红色就是黑色。

2、根结点一定为黑色。

3、所有的NULL指针被认为是黑色结点。
(在红黑树中,叶子节点指的不是度为0的节点,而是度为0的节点的两个null孩子)

4、没有两个连着的红结点。

5、从任一节点到其每个叶子的所有简单路径上的黑色结点数都相同。

与AVL树相比,红黑树是一种弱平衡的搜索二叉树:
AVL是保证了任意一个节点的平衡因子在[-1,0,1]之间,平衡性极高;
而红黑树的性质4保证了树中任两条路径长度相差不超过一倍;

因此ACL树在每次插入删除后一旦平衡因子绝对值大于1就要旋转,其插入、删除相对于红黑树而言效率较低;
相反的,因为平衡度高,所以AVL树查找效率也就更快。

但红黑树的平衡性也能保证它的最低查找效率为 logn。

由此可见,AVL树维护高度平衡而付出的代价是比较高的;红黑树的适当取舍使得其应用更广泛。

如:

  1. 广泛用于C ++的STL中,地图是用红黑树实现的;
  2. Linux的的进程调度,用红黑树管理进程控制块,进程的虚拟内存空间都存储在一颗红黑树上,每个虚拟内存空间都对应红黑树的一个节点,左指针指向相邻的虚拟内存空间,右指针指向相邻的高地址虚拟内存空间;
  3. IO多路复用的epoll采用红黑树组织管理sockfd,以支持快速的增删改查;
  4. Nginx中用红黑树管理定时器,因为红黑树是有序的,可以很快的得到距离当前最小的定时器;
  5. Java的TreeMap的实现;
  6. 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));
}
}

欢迎指出问题、共同进步!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值