数据结构的理解和应用——红黑树

心得:看了《数据结构和算法分析》,《算法导论》以及《算法》上关于红黑树的章节,总算把红黑树的实现和操作给理解了一边。虽然插入和删除的情况有些复杂,因为1,2年前看过一次,后来就忘了,感觉还是当时没有真正理解。

因此记住插入和删除的形式是不行的,关键是结合红黑树的5个特点,理解插入和删除中循环不变式的以及终止条件。这样才能知道为什么要弄一个“一重额外的黑色”才能知道为什么红黑树一次更新可以在O(1)中完成调整等等。

之后能够在脑海中演练,画画图,理解和AVL的异同要比把代码背下来有价值多了。

PS:《数据结构和算法分析》着重讲解了递归实现,但迭代应该更好一些,Java集合框架中采用的是parent引用+迭代的实现方式。

背景

2-3树和2-3-4树

B树具有有序性完美平衡性,但是插入和删除判断的情况太多了,还且需要额外的开销。通过将2-3树转换成左偏红黑树,2-3-4树转换成完全红黑树,可以用一点代价用更简单的方式处理:
以2-3树的转变为例:
2-3树中每个键值变为一个独立节点,一个3节点变为两个节点,中间中红色链接相连;
条件:
(1)红色链接均为左链接;
(2)没有任何节点同时和两个红色链接相连,很显然红色节点是一个3-节点分裂的;
(3)完美黑平衡(性质5);
相对的2-3-4树红色连接可以左右链接,但是同样不能有上下相邻的两个红色链接。将链接颜色转变为节点的属性,因此可以也就不能有相邻的红色节点(性质4),这条性质很重要,可以保持2-3-4树中4的阶数限制,这是便于操作和O(lgn)操作的保证;

1. 红黑树的基本条件

(1)节点不是黑的就是红的;
(2)根节点是黑色的;
(3)每个叶节点NIL是黑色的;
(4)红色节点的儿子必须是黑色的;
(5)每个节点到后代叶节点的简单路径上,均包含相同数目的黑色节点;

2. 红黑树的性能

一棵有n个内部节点的红黑树的高度至多为2lg(n+1);

(1)在查找,插入,删除操作的性能都是稳定O(log n)的,插入和删除操作中的上滤会执行O(lgn)次;
(2)任何不平衡都会在O(1)次(插入最多2次,删除最多三次)旋转之内解决;

3. 与AVL树的比较

(1)时间复杂度与AVL树相同,但统计性能比AVL树更高;
(2)如果插入和删除操作比较多,应当选用红黑树,AVL表现较差;
(3)如果数据分布较好可以采用AVL树;
(4)红黑树每次更新操作的旋转次数为O(1)(不超过3次),适合实现持久化的搜索树;

5. 结构和操作

5.1 结构

(1)使用一个共同的NIL叶节点,不过也可以直接直接使用NULL,Java中的实现就没有使用空对象,而是直接的null;
(2)5条特性;
(3)包含:parent引用,red(boolean),left,right,四个核心域;

5.2 旋转操作

基于p节点的左旋或右旋,主要是3组节点之间指针的调整(以左旋为例):
(1)p节点和其右儿子之间;
(2)p的右儿子的左儿子和p之间;
(3)p的右儿子和p的父节点之间;

5.3 插入操作(关键在于叔父节点的颜色和x的位置,变色+旋转)

插入操作和二叉搜索树一样在叶节点处插入新的节点x,但这个插入节点一开始是红色的,因此可能破坏性质(2)或性质(4);

被破坏的性质:
破坏性质(2)时,x是根节点,直接变为黑色,到所有路径黑高+1,就ok了;
破坏性质(4)时,x的父节点xp也是红色的,因此要想办法通过旋转“将一个多余的红色节点转过去”,那么x的叔父节点的颜色就很关键:

修复红黑树平衡条件
从2-3-4树角度分析,叔父节点是黑色,保证上面是一个3节点,情形一正是因为这时一个4节点,因此要继续上滤;
之所以要修复是因为x所在路径上有两个相邻的红色节点(x和xp),那么要想办法通过将一个红色节点(xp)变成黑色。
多出来一个黑色怎么办,这样就破坏性质(5)了,需要一次旋转,将祖父节点变成红色(祖父节点原来肯定是黑色,因为父节点是红色),父节点变为黑色旋转成为新的子树根,这样性质(5)保持,x和xp的颜色也正常了。但有个问题,祖父点变为了红色,因此只有在叔父节点是黑色时这样做才可行(否则又多一组相邻的红色节点)。
因此要分3种情况来处理,以xp为祖父节点的左儿子情形为例(还有一组对称的情形):
PS:x为“新插入节点”,xp为x的父节点,xpp为祖父节点,xppr为x的叔父节点;
插入

情形1:叔父节点是红色:
正如前面说的,这里无法解决问题,只好上滤,将xp和叔父节点变化黑色,祖父节点变成新的x;
实际上,可以对应2-3-4树中,xp,xpp,xppr是一个4节点,这时要分裂这个4节点,中键节点(xp节点)上移,xp变为黑色也就是分裂出来和x节点组成一个3节点,xppr变为黑色,变为一个2节点;

情形2:叔父节点是黑色,x是xp的右儿子:
前面说了,解决相邻的两个红色节点在于“变色+旋转”,关键是旋转之后,x变成了xpp的左儿子,x是红色,xpp也被变成了红色,因此x是xp右儿子时,也不能直接旋转。

通过一次左旋,将xp变成x的左儿子,这时x指向xp,那么就转化成了情形3;
对应一个2-3-4树,2和11节点对应一个3节点,7节点上滤要插入这个3节点,但是颜色分布不对,因此要通过一个左旋,变成情形3,再通过一次右旋更改颜色,这样2,7,11就组成了一个4节点;

情形3:叔父节点是黑色,x是xp的左儿子:
xp变为黑色,xpp变为红色,基于xpp一次右旋,这样两个相邻红色节点就隔开了,同时也没破坏别的性质。

因此情形1和情形2都是为了转换为情形3才最终解决问题。

5.4 删除操作

删除逻辑(删除节点p):
(1)如果p没有儿子节点,为红色,直接删掉,为黑色,需要调整,x就是p节点,调整完再删除p,删除一个红色节点相当于删除3-节点和4-节点中的非中间元素,不会破坏平衡性;
(2)如果p只有一个儿子节点,儿子节点代替p的位置,p不论颜色直接删掉,如果p为黑色,x就是儿子节点,进行调整;
(3)如果p有两个儿子节点,可以查找右子树中最左的节点或者左子树中最右节点(设为s),s的值和p的值交换,这个s就成了要删除的节点,而它最多只可能有一个儿子节点,适用于上面两种情况之一。

5.4.1 删除调整

本质上来说就是2-3-4树,分裂3-节点或4-节点,补充减少的黑高。
循环不变式:
按照算法导论上的设计,x所在路径少一个黑色,我们可以假设给它增加一重额外的黑色,这样在删除中同样保持一个性质就是黑高不变。这是一种概念上的设计。
如果x不是叶节点,以x为根的子树始终满足性质(5);

终止条件:
(1)x为红色节点,赋值为黑色,黑高将保持;
(2)x指向根节点,x都是根节点了剩下的黑高肯定一样了;
(3)满足情形4的条件,通过变色+旋转,”“补上了”“x所在路径的黑高;

删除的四种情形

这张图是算法导论中的,算法导论中使用NIL一个共享的单例哨兵,但这里还是使用null而不是哨兵,因此这里图中x不为null
PS:在算法导论中这个x可以是NIL节点,结合书中的删除算法,x节点不为NIL就是红色节点。实际上如果删除一个没有儿子节点的元素,这里的x就是这个元素(可以为黑色)。

PS:xp为父节点,w为兄弟节点,wr为兄弟的右儿子,wl为兄弟的左儿子,count(x)表示x的黑高。

以x是xp的左儿子为例
情形四:兄弟节点为黑色,它的右儿子是红色的:
首先分析情形四是因为我觉得另外三种情形只是在向情形四转变。通过一次左旋+变色,x这边路径终于增加了一个黑色节点,而wr从红色变成黑色,因此黑高也没有减少,故而解决了问题,对应B树的操作就是分裂了兄弟节点,兄弟节点的中键节点(D节点),上移,B下移成为独立节点。而图中B和C节点原来的颜色是什么,无关紧要,不影响结果,这大大简化了2-3-4树删除操作。

情形一:兄弟节点是红色:
兄弟节点w是红色,首先它一定会有两个黑色儿子节点。因为红黑树是完美黑平衡,因此有count(w) = count(x) + 1,而w是红色,那它必有两个黑色的儿子。这时通过一次在xp父亲节点上的左旋,x就可以得到一个黑色的兄弟节点。

情形二:兄弟节点为黑色,它的两个儿子是黑色:
如果C和E都是黑色或者为null,那么兄弟节点没有红色可以分裂出来,就继续上滤;兄弟节点w变成红色(黑高减一),x指向xp,因为x添加了额外一重黑色,因此实际路径黑高未减少;

情形三:兄弟节点为黑色,右儿子是黑色,左儿子是红色:
这种情况可以直接转化为情形四,通过一次在w节点上的右旋,C变为黑色,D变为红色,黑高保持不变,此时x的兄弟节点为黑色并且有红色的右儿子了。

6. 应用

(1)在Java中, TreeMap,Java 8中HashMap中TreeNode节点都采用了红黑树实现。
(2)C++中,STL的map和set也应用了红黑树;
(3)Linux进程调度Completely Fair Scheduler;
(4)用红黑树管理进程控制块epoll在内核中的实现,用红黑树管理事件块;
(5)Nginx中,用红黑树管理timer等;

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值