理解红黑树(上)插入操作
来源:互联网 发布:淘宝神器返利是真的吗 编辑:程序博客网 时间:2024/06/08 01:17
1. 引言
红黑树的结点增删改查效率非常优良,都为logn,其应用十分广泛:
1. Linux内核进程调度由红黑树管理进程控制块。
2. Epoll用红黑树管理事件块。
3. nginx服务器用红黑树管理定时器。
4. C++ STL中的map和set的底层实现为红黑树。
5. Java中的TreeMap和TreeSet由红黑树实现。
6. Java8开始,HashMap中,当一个桶的链表长度超过8,则会改用红黑树。
红黑树的操作比较复杂,实现起来比较麻烦,看了很多篇别人写的关于红黑树的博客,插入操作比较容易理解,但是删除操作都非常乱,本文不会讲解二叉排序树,左旋右旋等基础知识,将直接从红黑树的插入操作和删除操作开始讲解,并附上源码,算是自己对红黑树的一个总结。
2. 红黑树的性质
- 结点不是红色就是黑色。
- 根结点一定为黑色。
- 所有的NULL指针被认为是黑色结点。
- 红色结点的两个孩子一定是黑色。
- 从任一节点到其每个叶子的所有简单路径上的黑色结点数都相同。
想要理解红黑树的操作,以上性质必须滚瓜烂熟。
先定义红黑树这个类的成员属性:
public class RBTree<T extends Comparable<T>> { private RBNode<T> root;//根结点指针 private static final boolean RED = false; private static final boolean BLACK = true; class RBNode<T extends Comparable<T>> {//树的结点 boolean color;//红或黑 T key; RBNode<T> left;//左孩子指针 RBNode<T> right;//右孩子指针 RBNode<T> parent;//父结点指针,红黑树经常涉及到兄弟,叔叔,侄子,有个父结点指针方便操作。 public RBNode(boolean color, T key, RBNode<T> left, RBNode<T> right, RBNode<T> parent) { this.color = color; this.key = key; this.left = left; this.right = right; this.parent = parent; } } //左旋 private void leftRotate(RBNode<T> x) { RBNode<T> y = x.right; x.right = y.left; if(y.left != null) y.left.parent = x; y.parent = x.parent; if(x.parent != null) { if(x.parent.left == x) x.parent.left = y; else x.parent.right = y; }else { this.root = y; } y.left = x; x.parent = y; } //右旋 private void rightRotate(RBNode<T> x) { RBNode<T> y = x.left; x.left = y.right; if(y.right != null) y.right.parent = x; y.parent = x.parent; if(x.parent != null) { if(x.parent.left == x) x.parent.left = y; else x.parent.right = y; }else { this.root = y; } y.right = x; x.parent = y; }}
3. 红黑树的插入操作
首先红黑树是二叉排序树,按排序树的方式插入一个结点之后,此结点肯定是叶子结点,那么到底应该给这个新结点涂什么颜色,涂黑的话,当前的分支会多一个黑结点,破坏了性质5,要恢复很麻烦,而涂红则没有影响,所以直接对新插入的结点涂红。
然后可以判断一下这个新结点是不是根(刚建树),如果是根则涂黑,以满足性质2。
但是如果它有父结点,那就要根据父结点的颜色分情况讨论了:
(以下情况都假设父结点在左边,在右边的情况都是对称的)。
如果新插入结点的父结点为黑色,则什么都不用做。
插入结点是红的,不会破坏性质。(新结点也可以在右边)父结点为红色
如果父结点为红色,那由于性质2,说明父结点肯定不是根结点,那一定有祖父结点,且为黑色(性质4),叔叔结点可能有也可能为null。
从祖父结点来看,其父结点分支黑结点数为0(NULL这种东西不想多说),那么叔叔结点分支黑结点数也必须为0,所以进一步推断叔叔为null或者为红结点。那么就再分出这两种情况:
(1)叔叔为红结点的情况。
涂色之后,grandparent为根时的样子
grandparent不为根的情况,之后将其当成新结点进行操作
(2)叔叔为null时的情况
为了先解决parent这里的问题,肯定也只能将其涂黑再说,但是这样就多了一个黑结点,这里只有一种解决方法,就是对grandparent进行右旋,这样grandparent使右边多了一个黑结点,那么将其涂红即可。
要注意的是,之前的各种情况,新插入结点都是在左边,其实在右边也是一样的,但是这里就不同了,新插入的结点必须在左边,如果在右边,会在grandparent右旋之后成为其左孩子,这样一来,grandparent为红,新插入结点也为红,破坏了性质4,看下图。
所以应先对parent进行左旋操作,再将new和parent指针互换
最后对grandparent右旋变成:
到这里为止,红黑树的插入操作就全部解析完了。
4. 实现源码
//二叉排序树的插入private void insertNode(RBNode<T> node) { RBNode<T> curr = this.root, pre = null; int comp = 0; while(curr != null) { pre = curr; comp = node.key.compareTo(curr.key); if(comp < 0) curr = curr.left; else if(comp > 0) curr = curr.right; else return; } node.parent = pre; if(pre == null) this.root = node; else { if(comp < 0) pre.left = node; else if(comp > 0) pre.right = node; } insertFixUp(node);}//修复private void insertFixUp(RBNode<T> node){ RBNode<T> parent, gparent, uncle; //父结点不是根,且为红色,才进行修复 while((parent=node.parent) != null && parent.color == RED) { gparent = parent.parent; if(parent == gparent.left) {//父结点在左边,右边和左边对称,操作是一样的 if((uncle=gparent.right)!=null && uncle.color==RED) {//叔叔为红色 parent.color = BLACK; uncle.color = BLACK; gparent.color = RED; node = gparent; continue;//将祖父当成新插入结点继续修复 } //对于新插入结点在右边的情况,要将其转到左边来 if(parent.right == node) { leftRotate(parent); RBNode<T> tmp = parent; parent = node; node = tmp; } parent.color = BLACK; gparent.color = RED; rightRotate(gparent); }else {//对称操作,一样的 if((uncle=gparent.left)!=null && uncle.color==RED) { parent.color = BLACK; uncle.color = BLACK; gparent.color = RED; node = gparent; continue; } if(parent.left == node) { rightRotate(parent); RBNode<T> tmp = parent; parent = node; node = tmp; } parent.color = BLACK; gparent.color = RED; leftRotate(gparent); } } this.root.color = BLACK;}
5. 总结
可以看到,红黑树的插入操作还是蛮简单的,情况很少。
- 理解红黑树(上)插入操作
- InnoDB Insert(插入)操作(上)--mysql技术内幕
- 红黑树理解-插入删除
- 红黑树的插入操作详解(插入调整)
- 红黑树(二)红黑树的插入操作
- 红黑树(基本性质及插入操作)
- Red-Black Tree红黑树(插入操作)
- 红黑树(2) - 插入操作
- 红黑树的插入操作
- 红黑树、插入删除操作
- 红黑树插入删除操作
- 红黑树插入操作
- 红黑树的理解说明(插入)
- 插入排序(个人理解)
- 红黑树插入操作超详解
- 数据结构-----红黑树的插入操作
- 查找 -- 红黑树的插入操作
- MapOutputBuffer理解(上)
- 第三章 队列【数据结构】【链队列】【循环队列】
- 实现点击图片下载
- 1036. 跟奥巴马一起编程(15)
- 计算1!+2!+3!+...+n!
- Android命名规范(包名)
- 理解红黑树(上)插入操作
- 用户注册页面
- Error:Execution failed for task ':app:transformClassesWithProfilers-transformFor_360Debug'. > 2
- Python爬虫实战之爬取B站番剧信息(详细过程)
- UVALive 7672
- 1037. 在霍格沃茨找零钱(20)
- HTML
- centos7 虚拟机 连不上网 解决办法 win10 32位操作系统
- MyBatis(二)对表执行CRUD操作