对于红黑树的删除,看了数据结构的书,也看了很多网上的讲解和实现,但都不满意。很多讲解都是囫囵吞枣,知其然,不知其所以然,讲的晦涩难懂。

红黑树是平衡二叉树的一种,其删除算法是比较复杂的,因为删除后还要保持红黑树的特性。红黑树的特性如下:

    1. 节点是红色或黑色。
    2. 根是黑色。
    3. 所有叶子都是黑色(叶子是NIL节点)。
    4. 每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。)
    5. 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点(简称黑高)。

因此,从红黑树最基础的特性出发,抛开教科书和网上的算法,画了无数张图,分析了多种可能的情况以后,经过归纳提炼,实现了不同于教科书上的删除算法。

经过多次画图证明以后,笔者发现,红黑树的删除算法不是唯一的,不管如何调整,只要保证删除后还是一颗红黑树即可。

因此,笔者实现的 删除思路和算法如下:

  1. 删除转移:(这部分是大路货,不是自己实现的)

    • 如果被删除节点有两个非空子节点,则用后继节点的值代替该节点的值,这样演变成了删除后继节点;否则转下一条;
    • 如果被删除节点一个或两个孩子都为空:若有非空孩子,则用非空孩子节点替代之;若无,直接删除;
    • 删除后继节点:后继节点的左孩子节点一定为空,右孩子可能为空;处理如上一条;

  删除转移的目的是为了简化删除操作,更是为了简化修复操作。因为删除转移后,最终待删除的节点最多只会有一个非空孩子。

  2. 删除后修复:

  2.1 简单的情况:

    • 若被删除节点为红色节点,不需修复;此时该节点一定为红色的叶子节点(可根据红黑树的特性证明);
    • 若被删除的节点为黑色节点,且有一个非空子节点,则将其非空子节点颜色涂黑即可;

    对于以上两种简单的情况,做个说明:根据红黑树特性,非空子节点一定为红色节点,否则将违反特性;根据红黑树特性,在删除前,一颗红黑树不可能出现以下几种情况:

(图片来自网络,感谢原作者。)

  2.2 复杂的情况:删除后需要修复的。

    只有当被删除的节点为黑色叶子节点时,导致该节点所在的分支,少了一个黑色节点,树不再平衡,因此需要修复。

    修复的整体思路是:

    • 如果该节点的父节点、或兄弟节点、或兄弟节点的特定方向的子节点 中,有红色节点,则将此红色节点旋转过来,通过旋转、涂黑操作,保持自父节点以来的树的平衡;
    • 如果不满足上述条件,则通过旋转和变色操作,使其兄弟分支上也减少一个黑色节点,这样自父节点以来的分支保持了平衡,满足了条件,但对于父节点来说,其整个分支减少了一个黑色节点,需要递归向上处理,直至重新平衡,或者到达根节点;

  掌握了整体思路以后,就可编码实现了,编码中用了一些小技巧,合并了一些情况,代码比较简单易懂,阅读者可以根据代码的情况自己画图证明:

  说明:代码为dart语言实现,dart语法基本与Java一致,不清楚的地方可以参考:

    https://www.dartlang.org/guides/language/language-tour

   // 删除
bool delete(E value) {
var node = find(value);
if (node == null) return false;
_delete(node);
_nodeNumbers--;
return true;
} // 删除转移 并修复
void _delete(RBTNode<E> d) {
if (d.left != null && d.right != null) {
var s = _successor(d);
d.value = s.value;
d = s;
} var rp = d.left ?? d.right;
rp?.parent = d.parent;
if (d.parent == null)
_root = rp;
else if (d == d.parent.left)
d.parent.left = rp;
else
d.parent.right = rp; if (rp != null)
rp.paintBlack();
else if (d.isBlack && d.parent != null)
_fixAfterDelete(d.parent, d.parent.left == null);
} RBTNode<E> _successor(RBTNode<E> d) =>
d.right != null ? _minNode(d.right) : d.left; RBTNode<E> _minNode(RBTNode<E> r) => r.left == null ? r : _minNode(r.left); // fix up after delete
void _fixAfterDelete(RBTNode<E> p, bool isLeft) {
var ch = isLeft ? p.right : p.left;
if (isLeft) { // 如果被删除节点是父节点p的左分支;
if (p.isRed) { // 如果父节点为红,则兄弟节点ch一定为黑;
if (ch.left != null && ch.left.isRed) {
p.paintBlack();
_rotateRight(ch);
}
_rotateLeft(p);
} else if (ch.isRed) { // 兄弟节点为红,此时兄弟节点一定有两个非空黑色子节点;
p.paintRed();
ch.paintBlack();
_rotateLeft(p);
_fixAfterDelete(p, true); // 变换为父节点为红的情况,递归处理;
} else if (ch.left != null && ch.left.isRed) { // 父、兄均为黑,兄有红色左孩子;
ch.left.paintBlack();
_rotateRight(ch);
_rotateLeft(p);
} else { // 父兄均为黑,将父分支左右均减少一个黑节点,然后递归向上处理;
p.paintRed();
_rotateLeft(p);
if (ch.parent != null) _fixAfterDelete(ch.parent, ch == ch.parent.left);
}
} else { // symmetric
if (p.isRed) {
if (ch.right != null && ch.right.isRed) {
p.paintBlack();
_rotateLeft(ch);
}
_rotateRight(p);
} else if (ch.isRed) {
p.paintRed();
ch.paintBlack();
_rotateRight(p);
_fixAfterDelete(p, false);
} else if (ch.right != null && ch.right.isRed) {
ch.right.paintBlack();
_rotateLeft(ch);
_rotateRight(p);
} else {
p.paintRed();
_rotateRight(p);
if (ch.parent != null) _fixAfterDelete(ch.parent, ch == ch.parent.left);
}
}
}

  旋转操作的代码:

   void _rotateLeft(RBTNode<E> node) {
var r = node.right, p = node.parent;
r.parent = p;
if (p == null)
_root = r;
else if (p.left == node)
p.left = r;
else
p.right = r; node.right = r.left;
r.left?.parent = node;
r.left = node;
node.parent = r;
} void _rotateRight(RBTNode<E> node) {
var l = node.left, p = node.parent;
l.parent = p;
if (p == null)
_root = l;
else if (p.left == node)
p.left = l;
else
p.right = l; node.left = l.right;
l.right?.parent = node;
l.right = node;
node.parent = l;
}

红黑树的删除详解与思路分析——不同于教科书上的算法(dart语言实现)的更多相关文章

  1. stl map底层之红黑树插入步骤详解与代码实现

    转载注明出处:http://blog.csdn.net/mxway/article/details/29216199 本篇文章并没有详细的讲解红黑树各方面的知识,只是以图形的方式对红黑树插入节点需要进 ...

  2. POJ-2590-Steps题目详解,思路分析及代码,规律题,重要的是找到规律~~

    Steps Time Limit: 1000MS   Memory Limit: 65536K       http://poj.org/problem?id=2590 Description One ...

  3. RB-Tree删除详解

    红黑树的删除操作较于插入操作,情况更为复杂: 考虑到红黑节点的差异性,我们在此通过红黑节点来考虑这个问题,即仅仅通过要删除的节点是红节点,还是黑节点来讨论不同的情况: 1  删除的红节点为叶子结点(此 ...

  4. 关于syslog日志功能详解 事件日志分析、EventLog Analyzer

    关于syslog日志功能详解 事件日志分析.EventLog Analyzer 一.日志管理 保障网络安全 Windows系统日志分析 Syslog日志分析 应用程序日志分析 Windows终端服务器 ...

  5. Java性能分析之线程栈详解与性能分析

    Java性能分析之线程栈详解 Java性能分析迈不过去的一个关键点是线程栈,新的性能班级也讲到了JVM这一块,所以本篇文章对线程栈进行基础知识普及以及如何对线程栈进行性能分析. 基本概念 线程堆栈也称 ...

  6. 使用IDEA详解Spring中依赖注入的类型(上)

    使用IDEA详解Spring中依赖注入的类型(上) 在Spring中实现IoC容器的方法是依赖注入,依赖注入的作用是在使用Spring框架创建对象时动态地将其所依赖的对象(例如属性值)注入Bean组件 ...

  7. Block详解二(底层分析)

    Block专辑: Block讲解一 MRC-block与ARC-block Block详解一(底层分析) 今天讲述Block的最后一篇,后两篇仅仅是加深1,2篇的理解,废话少说,开始讲解! __blo ...

  8. 红黑树的删除操作---以JDK源码为例

    删除操作需要处理的情况: 1.删除的是红色节点,则删除节点并不影响红黑树的树高,无需处理. 2.删除的是黑色节点,则删除后,删除节点所在子树的黑高BH将减少1,需要进行调整. 节点标记: 正在处理的节 ...

  9. Qt QTreeWidget节点的添加+双击响应+删除详解(转)

    QTreeWidget是实现树形结构的类,在很多软件中都可以看到类似树形结构的界面. 我做的一个示例如下图,用来处理图像,最顶层节点是图像的路径名,子节点是图像的各个波段,双击各个波段会显示图像各波段 ...

随机推荐

  1. 配置ASP.NET Web应用程序, 使之运行在medium trust

    这文章会向你展示, 怎么配置ASP.NET Web应用程序, 使之运行在medium trust.   如果你的服务器有多个应用程序, 你可以使用code access security和medium ...

  2. bigworld源码分析(1)—— 研究bigworld的意义和目标

    对于网络游戏服务器开发熟悉的人,基本都知道bigworld引擎,此引擎包括客户端部分和服务器部分,已经有很多知名的网络游戏通过bigworld来构建游戏.我主要关注bigworld的服务器部分,它是一 ...

  3. [CareerCup] 2.2 Kth to Last Element of Linked List 链表的倒数第k个元素

    2.2 Implement an algorithm to find the kth to last element of a singly linked list. 这道题让我们求链表中倒数第k个元 ...

  4. 深入理解计算机系统第二版习题解答CSAPP 2.10

    对于任一位向量a,有a ^ a = 0.考虑下面的程序: void inplace_swap(int *x, int *y) { *y = *x ^ *y; *x = *x ^ *y; *y = *x ...

  5. django 表单提交 post 、get

    介绍 : django项目开发必须懂的知识点,下面使用的数据库是mysql , models.py  数据库表结构, # -*- coding: utf-8 -*-from __future__ im ...

  6. [转载] epoll详解

    转载自http://blog.csdn.net/xiajun07061225/article/details/9250579 什么是epoll epoll是什么?按照man手册的说法:是为处理大批量句 ...

  7. Java 管程解决生产者消费者问题

    同样是实验存档.//.. 依然以生产者消费者问题作为背景. 管程(=“资源管理程序”)将资源和对资源的操作封装起来,资源使用者通过接口操作资源就 ok,不用去考虑进程同步的问题. 管程: packag ...

  8. cs架构与bs架构的对比

    主要区别: Client/Server是建立在局域网的基础上的.Browser/Server是建立在广域网的基础上的. 1.硬件环境不同 C/S 一般建立在专用的网络上, 小范围里的网络环境, 局域网 ...

  9. JS里面的装箱和拆箱操作

    平日工作里,我想各位少侠对下面的用法都不陌生吧 var s1 = "abc"; var s2 = s1.indexOf("a") 还有例如什么indexOf() ...

  10. sql server 分组,取每组的前几行数据

    sql中group by后,获取每组中的前N行数据,目前我知道的有2种方法 比如有个成绩表: 里面有字段学生ID,科目,成绩.我现在想取每个科目的头三名. 1.   子查询 select * from ...