第 44 篇原创好文~
本文首发于政采云前端团队博客: 通俗易懂的红黑树图解(下)
![]()
回顾一下通俗易懂的红黑树图解(上),上篇首先介绍了二叉树的定义以及二叉树的查找,而后介绍了红黑树的五点性质以及红黑树的变色、左旋以及右旋等操做,最后结合变色、左旋及右旋详细讲解了插入节点的五种场景。而本篇通俗易懂的红黑树图解(下)是在上篇的基础上讲解红黑树最后一种操做-删除节点,删除节点相对插入节点会复杂一点,但经过分类概括出不一样的场景,能更容易理解和记忆。html
红黑树删除操做包括两部分,一是查找到删除节点,二是删除节点以及删除以后的自平衡。查找节点与二叉树的查找方式同样。而删除操做,当删除节点不存在时,结束本次删除操做;当删除节点存在时,删除节点,而后找到一个节点替换已删除的节点位置,从新链接上已删除节点的父节点与孩子节点。前端
以下图,删除节点 D ,须要找到一个节点能够替换到 D 节点位置,不然节点 P 和节点 L 及 R 之间的连接会断开,破坏了红黑树的性质,造成独立的树形结构。
关键字:查找节点
替换节点
node
查找删除节点与二叉树查找节点逻辑相同,经过与当前节点值比较,返回当前节点或者继续从左子树或者右子树继续查找。算法
在二叉查找树中查找节点 N ,首先从根节点开始,将根节点设置为当前节点,若当前节点为空,则查找失败,若 N 与当前节点值相等,返回当前节点,若 N 大于当前节点值,则从当前节点的右子节点开始查找,不然从当前节点的左子节点开始查找,直到返回目标节点或者查找失败;
回顾一下二叉查找树的性质:数据结构
根据二叉查找树的性质,删除节点以后,能够找到两个替换节点,便可以用左子树中的最大值以及右子树中的最小值来替换删除节点。框架
删除节点找替换节点又分三种情景:post
后继节点:删除节点的右子树中的最小节点,即右子树中最左节点。前继节点:删除节点的左子树中最大节点,即左子树中最右节点。性能
综上所述,寻找一个节点替换已删除节点位置,在不考虑节点值状况下,可等同于删除替换节点。网站
删除节点可等同于删除替换节点,因此节点删除就转换到了替换节点的各类场景。节点删除又分 9 种场景,在以下的描述场景中,场景 2 中的四种状况与场景 3 中的四种状况分别互为镜像,可参照对比着看。this
即替换的节点是红色节点,删除以后不影响红黑树的平衡,只须要把替换节点的颜色设成被删除节点的颜色便可从新平衡。
处理: 删除节点D,查找到替换节点R,R设成D节点的颜色,再替换D节点位置。
删除场景 2:替换节点是黑色节点、且是其父节点的左子节点
替换节点是黑色节点时,删除以后破坏了红黑树的平衡,须要考虑自平衡处理。而此又细分为 4 种场景。
场景 2.1:替换节点的兄弟节点是红色。删除黑色节点,左子树中黑色节点数减小一个,能够经过一些操做,达到间接借用红色的兄弟节点来补充左子树中黑色节点数。
处理:替换节点的父节点 P 设置红色、兄弟节点 S 设置成黑色,再对节点 P 左旋操做,变成场景 2.4。
场景 2.2:替换节点的兄弟节点是黑色且兄弟节点的右子节点是红色、左子节点任意颜色。一样是间接借用兄弟节点的红色右子节点补充到左子树中,达到红黑树的平衡。
处理:替换节点的兄弟节点 S 设置成父节点P的颜色,兄弟节点的右子节点 SR 设置为黑色,父节点P设置为黑色,再对节点 P 左旋操做。此时节点R替换到删除节点位置以后,红黑树从新达到平衡状态。
场景 2.3:替换节点的兄弟节点是黑色且兄弟节点的左子节点是红色,右子节点是黑色。
处理:替换节点的兄弟节点 S 设置成红色,兄弟节点的左子节点 SL 设置为黑色,再对节点 S 右旋操做,转换到了场景 2.2,再进行场景 2.2 的操做。
场景 2.4:替换节点的兄弟节点的左右子节点都是黑色。兄弟节点的子节点不能借用,就只能借用兄弟节点了。
处理:替换节点的兄弟节点 S 设置成红色,以父节点 P 看成替换节点,而后自底向上处理。
场景 3:替换节点是黑色节点、且是其父节点的右子节点。(与场景 2 镜像)
场景 3.1:替换节点的兄弟节点是红色。
处理:替换节点的父节点 P 设置红色、兄弟节点 S 设置成黑色,再对节点 P 右旋操做,变成场景3.4。
场景 3.2:替换节点的兄弟节点是黑色且兄弟节点的左子节点是红色、右子节点任意颜色。
处理:替换节点的兄弟节点 S 设置成父节点 P 的颜色,兄弟节点的左子节点 SL 设置为黑色,父节点P 设置为黑色,再对节点 P 右旋操做。此时节点 R 替换到删除节点位置以后,红黑树从新达到平衡状态。
场景 3.3:替换节点的兄弟节点是黑色且兄弟节点的右子节点是红色、左子节点为黑色。
处理:替换节点的兄弟节点 S 设置成红色,兄弟节点的右子节点 SL 设置为黑色,再对节点S左旋操做,转换到了场景 3.2,再进行场景 3.2 的操做。
场景 3.4:替换节点的兄弟节点的左右子节点都是黑色。
处理:替换节点的兄弟节点 S 设置成红色,以父节点 P 看成替换节点,而后自底向上处理。
节点删除及平衡代码:
/** * 查找节点 * @param key 节点key值 */ search(key) { let node = this.root while (node) { if (key < node.key) { node = node.left } else if (key > node.key) { node = node.right } else if (key === node.key) { break } } return node } /** * 替换u节点,重置v节点 * @param u 待删除节点 * @param v 子节点 */ const replace = function(u, v) { if(!u.parent){ // u是根节点,设置v为根节点 this.root = v } else if(u === u.parent.left){ // 重置u的父节点的左节点 u.parent.left = v } else { // 重置u的父节点的右节点 u.parent.right = v } // 重置v的父节点 v.parent = u.parent } /** * 查找node节点的后继节点 */ findSuccessor(node) { while (node.left) { node = node.left; } return node; } /** * 删除节点 * @param key 删除节点key值 */ delete(key) { const node = search(key) if(!node){ return } let fix let color = node.color if(!node.left){ //左节点为空值 fix = node.right this.replace(node, node.right) } else if(!node.right){ //右节点为空值 fix = node.left this.replace(node, node.left) } else { // 左右节点都不为空值 const successor = this.findSuccessor(node.right) //替换节点的颜色 color = successor.color //后继节点只存在右节点或者两个nil子节点状况 fix = successor.right //若是后继节点是父节点的非直接子节点 if(successor.parent !== node){ this.replace(successor, successor.right) successor.right = node.right successor.right.parent = successor } this.replace(node, successor) successor.color = node.color successor.left = node.left successor.left.parent = successor } if(color === Color.BLACK){ this.balanceDeletion(fix) } } /** * 删除节点平衡修正 * @param node 节点 */ balanceDeletion(node) { while (node !== this.root && node.color === Color.BLACK) { // 节点是父节点的左子节点 if (node === node.parent.left) { //兄弟节点 let sibling = node.parent.right; if (sibling.color === Color.RED) { // 场景2.1:兄弟节点是红色 // 兄弟节点设置为黑色 sibling.color = Color.BLACK; //替换节点的父节点设置为红色 node.parent.color = Color.RED; // 左旋 this.rotateLeft(node.parent); sibling = node.parent.right; } if (sibling.left.color === Color.BLACK && sibling.right.color === Color.BLACK) { // 场景2.4: 兄弟节点两个子节点都是黑色 sibling.color = Color.RED; //再次以父节点为新节点做自平衡处理。 node = node.parent; continue; } else if (sibling.left.color === Color.RED) { // 场景2.3: 兄弟节点的左子节点是黑色,转换到场景2.2. sibling.left.color = Color.BLACK; sibling.color = Color.RED; //对兄弟节点右旋 this.rotateRight(sibling) sibling = node.parent.right; } if (sibling.right.color === Color.RED) { //场景2.2:兄弟节点的右节点是红色 sibling.color = node.parent.color; node.parent.color = Color.BLACK; sibling.right.color = Color.BLACK; //对父节点左旋 this.rotateLeft(node.parent); // 左旋以后,红黑树从新平衡 node = this.root; } } else { //节点是父节点的左节点 let sibling = node.parent.left; if (sibling.color === Color.RED) { // 场景 3.1:替换节点的史弟节点是红色 sibling.color = Color.BLACK; node.parent.color = Color.RED; this.rotateRight(node.parent); sibling = node.parent.left; } if (sibling.right.color === Color.BLACK && sibling.left.color === Color.BLACK) { //场景3.4:替换节点的两个子节点都是黑色 sibling.color = Color.RED; //再次以父节点为新节点做自平衡处理。 node = node.parent; continue } else if (sibling.right.color === Color.RED) { // 场景3.3:兄弟节点的右子节点是红色 sibling.right.color = Color.BLACK; sibling.color = Color.RED; this.rotateLeft(sibling); sibling = node.parent.left; } if (sibling.left.color === Color.RED) { // 场景3.2:兄弟节点的左子节点是红色 sibling.color = node.parent.color; node.parent.color = Color.BLACK; sibling.left.color = Color.BLACK; this.rotateRight(node.parent); node = this.root; } } } node.color = Color.BLACK; } }
红黑树普遍用在 Java 的集合框架 (HashMap、TreeMap、TreeSet)、Nginx 的 Timer 管理、Linux 虚拟内存管理以及 C++ 的 STL 等等场景。
在 Linux 内核中,每一个用户进程均可以访问 4GB 的线性虚拟空间,虚拟空间每每须要多个虚拟内存区域描述,对这些内存区域,Linux 内核采用了链表以及红黑树形式组织。内存区域按地址排序,连接成一个链表以及一颗红黑树,寻找空闲区域时只须要遍历这个链表,在发生缺页中断时经过红黑树快速检索特定内存区域。
红黑树的删除操做就基本介绍完了,总结一下删除操做就是,删除节点等同于删除替换节点,若替换节点是红色节点时,直接删除不会影响平衡;若替换节点是黑色节点时,就须要借用兄弟节点的右子节点、左子节点或者兄弟节点。
红黑树最吸引人的是它的全部操做在最差状况下能够保证 O(logN) 的时间复杂度,稳定且高效。例如要在10 万条(2^20)数据中查找一条数据,只须要 20 次的操做就能完成。但这些保证有一个前置条件,就是数据量不大,且数据能够彻底放到内存中。在数据量比较大时,由于红黑树的深度比较大形成磁盘 IO 的频繁读写,会致使它的效率低下。
另外推荐 Data Structure Visualizations 网站,它包含很是多的数据结构方面的可视化算法题。其中就有 红黑树的算法,对照着在线生成的红黑树看,会更容易理解红黑树中各类操做场景。
政采云前端团队(ZooTeam),一个年轻富有激情和创造力的前端团队,隶属于政采云产品研发部,Base 在风景如画的杭州。团队现有 50 余个前端小伙伴,平均年龄 27 岁,近 3 成是全栈工程师,妥妥的青年风暴团。成员构成既有来自于阿里、网易的“老”兵,也有浙大、中科大、杭电等校的应届新人。团队在平常的业务对接以外,还在物料体系、工程平台、搭建平台、性能体验、云端应用、数据分析及可视化等方向进行技术探索和实战,推进并落地了一系列的内部技术产品,持续探索前端技术体系的新边界。
若是你想改变一直被事折腾,但愿开始能折腾事;若是你想改变一直被告诫须要多些想法,却无从破局;若是你想改变你有能力去作成那个结果,却不须要你;若是你想改变你想作成的事须要一个团队去支撑,但没你带人的位置;若是你想改变既定的节奏,将会是“5 年工做时间 3 年工做经验”;若是你想改变原本悟性不错,但老是有那一层窗户纸的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但愿参与到随着业务腾飞的过程,亲手推进一个有着深刻的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我以为咱们该聊聊。任什么时候间,等着你写点什么,发给 ZooTeam@cai-inc.com