红黑树删除操作学习笔记
2016-12-20 18:22
316 查看
最近在尝试实现一个简单的STL,到了红黑树那个位置,由于侯捷老师的那本书只讲了插入操作,所以只能自己找资料学习删除操作,在此总结记录一下。
首先是红黑树的几条性质,各种操作最终都要保证满足这些性质。
1、每个节点或是红的,或是黑的。
2、根节点是黑的。
3、每个叶节点(NIL)是黑的。
4、如果一个节点是红的,则它的两个儿子都是黑的。
5、对于每个节点,从该节点到其子孙节点的所有路径上包含相同数目的黑节点。
至于为什么这些性质可以保持平衡,可以参考 2-3-4 树。
接下来进入正题
删除操作大体可以分为两步,首先是删除节点,然后是如果有需要,则调整红黑树使其平衡。
下面是删除节点的伪代码。
删除操作类似于二叉搜索树的删除操作,这里 z 为要删除的节点,y 为实际删除的节点,x 为 y 的孩子节点,这里 z 和 y 可能相等。
大概有这么几种情况:
如果 y 是黑的,会产生几个问题:
1、如果 y 是根节点,而 x 是红的,则违反了性质 2,这种情况只要简单把 x 改为黑的即可。
2、如果 y 的父节点是红的,而 x 也是红的,则违反了性质 4。
3、删除 y 节点会导致该路径上的黑节点数目比其他少 1,违反了性质 5。
如果 y 是红的,可以发现删除后路径上黑节点数目不变,即不违反性质 5,而由于此时 y 的父节点一定是黑的(否则原本的树违反性质 4),所以无论 x 是什么颜色都不会违反性质 4。
接下来是平衡红黑树。
根据上面的分析,删除黑节点后会导致树至少违反性质 5。
但是有一种办法可以保持性质 5,就是把 y 的黑色推到 x 身上,也就是说 x 可能是黑黑或者黑红,这样违反了性质 1,而上面的算法可以用来恢复性质 1,一共有八种情况,这里只列举了四种情况(其余四种反向处理),下面这张图中,x 恒为具有双重颜色的节点。
注意,以 case2 的右边那张图为例,这里 B 的颜色偏淡,代表颜色不确定(可红可黑),而 D 的颜色才代表红色。
case1: x 的兄弟 w 是红的
这种情况下,对 w 和 x 的父节点做一次左旋转并改变颜色,旋转后依然保持性质 5。那么问题是,为什么要旋转呢?注意,这时候 x 的兄弟 w 变成了 C 节点,而且是黑的。这里旋转的目的就是让 x 的兄弟节点变为黑的。那么为什么要将 x 的兄弟节点变成黑的呢?目的是形成 case2、case3 或 case4。
case2:x 的兄弟 w 是黑的,且 w 的两个孩子都是黑的
这种情况是最简单的,由于 w 是和两个孩子都是黑的,所以可以将 x 和 w 都去掉一层黑色,将这层黑色转移给父节点,也就是 x 变为黑的, w 变为红的,并将父节点(具有双重颜色)作为新的 x 节点继续处理。此时如果父节点原本是红的,则退出外层循环,然后将新的 x 节点(即父节点)变为黑的,完成平衡操作。如果父节点原本是黑的,则父节点为黑黑,继续循环。也就形成了 case1、case2、case3 或 case4。
case3:x 的兄弟 w 是黑的,且 w 的左孩子是红的,右孩子是黑的
这种情况下,对 w 进行一次右旋转,旋转后依然保持性质 5。这里旋转的目的是让 w 的右孩子变成红的,也就是形成 case4。
case4:x 的兄弟 w 是黑的,且 w 的右孩子是红的
这种情况下,对 w 和 x 的父节点做一次左旋转并改变颜色,旋转后 E 节点变为黑色,A 节点增加了一个黑色父节点(路径上的黑色节点数目加 1),也就是此时 A 节点可以去掉一层黑色且不破坏性质 5 同时恢复了性质 1。最后将根节点赋值给 x 用以结束循环。
总的来说,case1 和 case3 的操作都是为了形成其他情况,而位于 case2 时,改变颜色后,如果父节点原本为红,则改为黑色结束平衡,原本为黑则继续循环,直至到达根节点,此时可以简单地去掉根节点的一重黑色而不破坏性质 5。case4 则巧妙利用节点间的关系,通过旋转和改变颜色,使左侧多出一个黑色节点,即令 x 节点可以去掉一层黑色,保持性质 5 且恢复性质 1。
参考资料
- 算法导论(原书第3版)
-
STL源码剖析---红黑树原理详解下
首先是红黑树的几条性质,各种操作最终都要保证满足这些性质。
1、每个节点或是红的,或是黑的。
2、根节点是黑的。
3、每个叶节点(NIL)是黑的。
4、如果一个节点是红的,则它的两个儿子都是黑的。
5、对于每个节点,从该节点到其子孙节点的所有路径上包含相同数目的黑节点。
至于为什么这些性质可以保持平衡,可以参考 2-3-4 树。
接下来进入正题
删除操作大体可以分为两步,首先是删除节点,然后是如果有需要,则调整红黑树使其平衡。
下面是删除节点的伪代码。
RB-DELETE(T, z) if left[z] = nil[T] or right[z] = nil[T] then y <- z else y <- TREE-SUCCESSOR(z) if left[y] ≠ nil[T] then x <- left[y] else x <- right[y] p[x] <- p[y] if p[y] = nil[T] then root[T] <- x else if y = left[p[y]] then left[p[y]] <- x else right[p[y]] <- x if y ≠ z then key[z] <- key[y] copy y's satellite data into z if color[y] = BLACK then RB-DELETE-FIXUP(T, x) return y
删除操作类似于二叉搜索树的删除操作,这里 z 为要删除的节点,y 为实际删除的节点,x 为 y 的孩子节点,这里 z 和 y 可能相等。
大概有这么几种情况:
// x(NIL) 表示 x 可能为 NIL // z == y z(=y) z(=y) / \ / \ x NIL NIL x(NIL) // z != y z z / \ / \ o o o y / \ / \ y o NIL x(NIL) / \ NIL x(NIL)最后需要根据 y 的颜色判断是否需要重新平衡红黑树。
如果 y 是黑的,会产生几个问题:
1、如果 y 是根节点,而 x 是红的,则违反了性质 2,这种情况只要简单把 x 改为黑的即可。
2、如果 y 的父节点是红的,而 x 也是红的,则违反了性质 4。
3、删除 y 节点会导致该路径上的黑节点数目比其他少 1,违反了性质 5。
如果 y 是红的,可以发现删除后路径上黑节点数目不变,即不违反性质 5,而由于此时 y 的父节点一定是黑的(否则原本的树违反性质 4),所以无论 x 是什么颜色都不会违反性质 4。
接下来是平衡红黑树。
RB-DELETE-FIXUP(T, x) while x ≠ root[T] and color[x] = BLACK do if x = left[p[x]] then w <- right[p[x]] if color[w] = RED // case 1 then color[w] <- BLACK color[p[x]] <- RED LEFT-ROTATE(T, p[x]) w <- right[px[x]] if color[left[w]] = BLACK and color[right[w]] = BLACK // case 2 then color[w] <- RED x <- p[x] else if color[right[w]] = BLACK // case 3 then color[left[w]] <- BLACK color[w] <- RED RIGHT-ROTATE(T, w) w <- right[p[x]] // case 4 color[w] <- color[p[x]] color[p[x]] <- BLACK color[right[w]] <- BLACK LEFT-ROTATE(T, p[x]) x <- root[T] else (same as then clause with "right" and "left" exchanged) color[x] <- BLACK
根据上面的分析,删除黑节点后会导致树至少违反性质 5。
但是有一种办法可以保持性质 5,就是把 y 的黑色推到 x 身上,也就是说 x 可能是黑黑或者黑红,这样违反了性质 1,而上面的算法可以用来恢复性质 1,一共有八种情况,这里只列举了四种情况(其余四种反向处理),下面这张图中,x 恒为具有双重颜色的节点。
注意,以 case2 的右边那张图为例,这里 B 的颜色偏淡,代表颜色不确定(可红可黑),而 D 的颜色才代表红色。
case1: x 的兄弟 w 是红的
这种情况下,对 w 和 x 的父节点做一次左旋转并改变颜色,旋转后依然保持性质 5。那么问题是,为什么要旋转呢?注意,这时候 x 的兄弟 w 变成了 C 节点,而且是黑的。这里旋转的目的就是让 x 的兄弟节点变为黑的。那么为什么要将 x 的兄弟节点变成黑的呢?目的是形成 case2、case3 或 case4。
case2:x 的兄弟 w 是黑的,且 w 的两个孩子都是黑的
这种情况是最简单的,由于 w 是和两个孩子都是黑的,所以可以将 x 和 w 都去掉一层黑色,将这层黑色转移给父节点,也就是 x 变为黑的, w 变为红的,并将父节点(具有双重颜色)作为新的 x 节点继续处理。此时如果父节点原本是红的,则退出外层循环,然后将新的 x 节点(即父节点)变为黑的,完成平衡操作。如果父节点原本是黑的,则父节点为黑黑,继续循环。也就形成了 case1、case2、case3 或 case4。
case3:x 的兄弟 w 是黑的,且 w 的左孩子是红的,右孩子是黑的
这种情况下,对 w 进行一次右旋转,旋转后依然保持性质 5。这里旋转的目的是让 w 的右孩子变成红的,也就是形成 case4。
case4:x 的兄弟 w 是黑的,且 w 的右孩子是红的
这种情况下,对 w 和 x 的父节点做一次左旋转并改变颜色,旋转后 E 节点变为黑色,A 节点增加了一个黑色父节点(路径上的黑色节点数目加 1),也就是此时 A 节点可以去掉一层黑色且不破坏性质 5 同时恢复了性质 1。最后将根节点赋值给 x 用以结束循环。
总的来说,case1 和 case3 的操作都是为了形成其他情况,而位于 case2 时,改变颜色后,如果父节点原本为红,则改为黑色结束平衡,原本为黑则继续循环,直至到达根节点,此时可以简单地去掉根节点的一重黑色而不破坏性质 5。case4 则巧妙利用节点间的关系,通过旋转和改变颜色,使左侧多出一个黑色节点,即令 x 节点可以去掉一层黑色,保持性质 5 且恢复性质 1。
参考资料
- 算法导论(原书第3版)
-
STL源码剖析---红黑树原理详解下
相关文章推荐
- 红黑树学习笔记(3)-删除操作
- php学习笔记3--文件系统的操作(创建、打开及批量删除)
- 【MongoDB学习笔记7】深入MongoDB的删除(remove/drop)操作
- OAF学习笔记-7-delete 删除 的操作
- OAF学习笔记-7-delete 删除 的操作
- [原创]java WEB学习笔记66:Struts2 学习之路--Struts的CRUD操作( 查看 / 删除/ 添加) 使用 paramsPrepareParamsStack 重构代码 ,PrepareInterceptor拦截器,paramsPrepareParamsStack 拦截器栈
- OAF学习笔记-7-delete 删除 的操作
- OpenLDAP学习笔记8——LDAP常用操作:添加、删除、修改、搜索
- OAF学习笔记-7-delete 删除 的操作
- OAF学习笔记-7-delete 删除 的操作
- [原创]java WEB学习笔记65:Struts2 学习之路--Struts的CRUD操作( 查看 / 删除/ 添加) ModelDriven拦截器 paramter 拦截器
- 学习笔记——C语言实现单链表的基本操作:创建、输出、插入结点、删除结点、逆序链表
- OAF学习笔记-7-delete 删除 的操作
- Jquery学习笔记:删除节点的操作
- OAF学习笔记-7-delete 删除 的操作
- 学习笔记——C语言实现单链表的基本操作:创建、输出、插入结点、删除结点、逆序链表
- MySQL学习笔记2:数据库的基本操作(创建删除查看)
- OAF学习笔记-7-delete 删除 的操作
- 学习笔记:B树建立,搜索和删除操作
- MongoDB快速入门学习笔记6 MongoDB的文档删除操作