红黑树是数据结构中比较复杂的一种,最近与它交集颇多,因而花了一周的空闲时间跟它死磕,终于弄明白并实现了红黑树。写文总结一下,但愿能给试图理解红黑树的同窗一些灵感,也让我能记得更深入。html
在研究红黑树时吃了很多苦头,缘由有二:git
双黑、caseN
法,而插入和删除的状况不少,每种都有对应的处理方式,若是死记硬背的话,再过一段时间再回忆各类状况可能就一头雾水了。网络上讲红黑树的实现多来源于《算法导论》一书,直接讲红黑树的实现,须要处理颜色和高度两种属性约束,比较晦涩。本文经过红黑树的等同—— 2-3-4树,避开颜色属性约束,也弱化了高度的影响,以另外一种方式去理解红黑树,虽然并不能彻底下降它的复杂度,但自认为较之广泛实现,更易记一些。github
文章最前面先放上红黑树的实现源码,代码在 Github 上,一开始实现时使用我最熟练的 PHP,后续添加了 Java 版,代码均可以直接运行。源码连接:GitHub-枕边书-RBTree
,欢迎star
。算法
文章欢迎转载,请注明出处:http://www.cnblogs.com/zhenbianshu/p/8185345.html。编程
红黑树是一种结点带有颜色属性的二叉查找树,但它在二叉查找树以外,还有如下要求:网络
下图就是一个典型的红黑树:数据结构
但实现上我省略了其中的 Nil 结点,通常以下图,你们理解时也能够忽略它们。编程语言
咱们知道二叉查找树在不停地添加或删除结点后,可能会致使结点状况以下:优化
这种状况下,二叉查找树的查找效率最坏会下降为 O(n)
。debug
而红黑树因为在插入和删除结点时都会进行变色旋转等操做,在符合红黑树条件的状况下,即便一边子树全是黑色结点,另外一边子树全是红黑相间,两子树的高度差也不会超过一半。一棵有 n 个结点的红黑树高度至多为 2log(n+1)
,查找效率最坏为 O(log(n))
。
因此红黑树常被用于需求查找效率稳定的场景,如 Linux 中内核使用它管理内存区域对象、Java8 中 HashMap 的实现等,因此了解红黑树也颇有意义。
下面介绍一下红黑树的等同 2-3-4树。
2-3-4树是四阶的 B树(Balance Tree),它的结构有如下限制:
节点只能是 2-节点、3-节点、4-节点之一。
元素始终保持排序顺序,总体上保持二叉查找树的性质,即父结点大于左子结点,小于右子结点;并且结点有多个元素时,每一个元素必须大于它左边的和它的左子树中元素。
下图是一个典型的 2-3-4树(来自维基百科):
2-3-4树的查询操做像普通的二叉搜索树同样,很是简单,但因为其结点元素数不肯定,在一些编程语言中实现起来并不方便,实现通常使用它的等同——红黑树。
至于为何说红黑树是 2-3-4树的一种等同呢,这是由于 2-3-4树的每个结点都对应红黑树的一种结构,因此每一棵 2-3-4树也都对应一棵红黑树,下图是 2-3-4树不一样结点与红黑树子树的对应。
而上文中的 2-3-4树也能够转换成一棵红黑树:
由红黑树的性质5,和 2-3-4树的性质1,为了便于理解红黑树和 2-3-4树的对应关系,咱们能够把红黑树从根结点到叶子结点的黑色结点个数定义为高度
。
红黑树和 2-3-4树的结点添加和删除都有一个基本规则:避免子树高度变化,由于不管是 2-3-4树仍是红黑树,一旦子树高度有变更,势必会影响其余子树进行调整,因此咱们在插入和删除结点时尽可能经过子树内部调整来达到平衡,2-3-4树实现平衡是经过结点的旋转和结点元素数变化,红黑树是经过结点旋转和变色。
下面来对照着 2-3-4树说一下红黑树结点的添加和删除:
2-3-4树中结点添加须要遵照如下规则:
而将这些规则对应到红黑树里,就是:
红色
,这样才可能不会对红黑树的高度产生影响。黑+红
子树,插入后将其修复成 红+黑+红
子树(对应 3-结点升元);红+黑+红
子树,插入后将其修复成红色祖父+黑色父叔+红色孩子
子树,而后再把祖父结点当成新插入的红色结点递归向上层修复,直至修复成功或遇到 root 结点;如上图所示,虽然向红黑树中插入了一个新结点,但因为旋转和变色,子树的高度保持不变。
红黑树的删除要比插入要复杂一些,咱们仍是类比 2-3-4树来说:
将这些规则对应到红黑树中即:
替代结点
(左子树的最右结点或右子树的最左结点都能保证替换后保证二叉查找树的结点的排序性质,叶子结点的替代结点是自身)替换掉被删除结点,从替代的叶子结点向上递归修复;替代结点为黑色(对应 2-3-4树中 2-结点)时,意味着替代结点所在的子树会降一层,须要依次检验如下三项,以恢复子树高度:
父结点看成替代结点
递归向上处理。如上图,删除的要点是 找到替代结点
,若是替代结点是黑色,递归向上依次判断侄子结点、父结点是否能够补充被删除的黑色,总体思想就是将删除一个黑色结点形成的影响局限在子树内处理。
固然实现过程当中调试也占了很大一部分,我使用了两项方法帮助调试:
debug
属性,用二分法设置此属性来找到问题结点;printTree()
方法,实时打印树结构,肯定代码问题再分析;因为红黑树相对其余树实在较为复杂,只经过思考就彻底理解不太现实,还须要本身去试着画,试着实现,我画了 5 张 A4 纸的正反面才算理解了红黑树,即使如此,在写这篇文章时还发现了代码中的可优化点。
并且代码实现比画图还略复杂,理论中的一个旋转
就包含了 左旋/右旋/先左旋再右旋/先右旋再左旋
几种状况,虽然有必定规律,仍是本身实现一下印象最深入。
关于本文有什么问题能够在下面留言交流,若是您以为本文对您有帮助,能够点击下面的 推荐
支持一下我,博客一直在更新,欢迎 关注
。