通过2-3-4树理解红黑树

前言

红黑树是数据结构中比较复杂的一种,最近与它交集颇多,于是花了一周的空闲时间跟它死磕,终于弄明白并实现了红黑树。写文总结一下,希望能给试图理解红黑树的同学一些灵感,也让我能记得更深刻。

在研究红黑树时吃了不少苦头,原因有二:

  • 红黑树的插入和删除非常复杂,很多人并没有理解或完全实现,或实现了的没有任何注释,让人很难参考;
  • 网络上红黑树的理解方式较为单一,一般是 双黑、caseN 法,而插入和删除的情况很多,每种都有对应的处理方式,如果死记硬背的话,再过一段时间再回忆各种情况可能就一头雾水了。

网络上讲红黑树的实现多来源于《算法导论》一书,直接讲红黑树的实现,需要处理颜色和高度两种属性约束,比较晦涩。本文通过红黑树的等同—— 2-3-4树,避开颜色属性约束,也弱化了高度的影响,以另一种方式去理解红黑树,虽然并不能完全降低它的复杂度,但自认为较之普遍实现,更易记一些。

文章最前面先放上红黑树的实现源码,代码在 Github 上,一开始实现时使用我最熟练的 PHP,后续添加了 Java 版,代码都可以直接运行。源码链接:GitHub-枕边书-RBTree,欢迎star

文章欢迎转载,请注明出处:http://www.cnblogs.com/zhenbianshu/p/8185345.html。


红黑树

定义

红黑树是一种结点带有颜色属性的二叉查找树,但它在二叉查找树之外,还有以下要求:

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

下图就是一个典型的红黑树:

但实现上我省略了其中的 Nil 结点,一般如下图,大家理解时也可以忽略它们。

优势和用途

我们知道二叉查找树在不停地添加或删除结点后,可能会导致结点情况如下:

这种情况下,二叉查找树的查找效率最坏会降低为 O(n)

而红黑树由于在插入和删除结点时都会进行变色旋转等操作,在符合红黑树条件的情况下,即使一边子树全是黑色结点,另一边子树全是红黑相间,两子树的高度差也不会超过一半。一棵有 n 个结点的红黑树高度至多为 2log(n+1),查找效率最坏为 O(log(n))

所以红黑树常被用于需求查找效率稳定的场景,如 Linux 中内核使用它管理内存区域对象、Java8 中 HashMap 的实现等,所以了解红黑树也很有意义。

下面介绍一下红黑树的等同 2-3-4树。


2-3-4树

定义

2-3-4树是四阶的 B树(Balance Tree),它的结构有以下限制:

  • 所有叶子节点都拥有相同的深度。
  • 节点只能是 2-节点、3-节点、4-节点之一。
    • 2-节点:包含 1 个元素的节点,有 2 个子节点;
    • 3-节点:包含 2 个元素的节点,有 3 个子节点;
    • 4-节点:包含 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树中结点添加需要遵守以下规则:

  • 插入都是向最下面一层插入;
  • 升元:将插入结点由 2-结点升级成 3-结点,或由 3-结点升级成 4-结点;
  • 向 4-结点插入元素后,需要将中间元素提到父结点升元,原结点变成两个 2-结点,再把元素插入 2-结点中,如果父结点也是 4-结点,则递归向上层升元,至到根结点后将树高加1;

而将这些规则对应到红黑树里,就是:

  • 新插入的结点颜色为红色,这样才可能不会对红黑树的高度产生影响。
  • 2-结点对应红黑树中的单个黑色结点,插入时直接成功(对应 2-结点升元)。
  • 3-结点对应红黑树中的黑+红子树,插入后将其修复成 红+黑+红 子树(对应 3-结点升元);
  • 4-结点对应红黑树中的红+黑+红子树,插入后将其修复成红色祖父+黑色父叔+红色孩子子树,然后再把祖父结点当成新插入的红色结点递归向上层修复,直至修复成功或遇到 root 结点;

如上图所示,虽然向红黑树中插入了一个新结点,但由于旋转和变色,子树的高度保持不变。


删除结点

红黑树的删除要比插入要复杂一些,我们还是类比 2-3-4树来讲:

  • 查找最近的叶子结点中的元素替代被删除元素,删除替代元素后,从替代元素所处叶子结点开始处理;
  • 降元:4-结点变 3-结点,3-结点变 2-结点;
  • 2-结点中只有一个元素,所以借兄弟结点中的元素来补充删除后的造成的空结点;
  • 当兄弟结点中也没有多个元素可以补充时,尝试将父结点降元,失败时向上递归,至到子树降元成功或到 root 结点树高减1;

将这些规则对应到红黑树中即:

  • 查找离当前结点最近的叶子结点作为替代结点(左子树的最右结点或右子树的最左结点都能保证替换后保证二叉查找树的结点的排序性质,叶子结点的替代结点是自身)替换掉被删除结点,从替代的叶子结点向上递归修复;
  • 替代结点颜色为红色(对应 2-3-4树中 4-结点或 3-结点)时删除子结点直接成功;
  • 替代结点为黑色(对应 2-3-4树中 2-结点)时,意味着替代结点所在的子树会降一层,需要依次检验以下三项,以恢复子树高度:
    • 兄弟结点的子结点中有红色结点(兄弟结点对应 3-结点或 4-结点)能够“借用”,旋转过来后修正颜色;
    • 父结点是红色结点(父结点对应 3-结点或 4-结点,可以降元)时,将父结点变黑色,自身和兄弟结点变红色后删除;
    • 父结点和兄弟结点都是黑色时,将子树降一层后把父结点当作替代结点递归向上处理。

如上图,删除的要点是 找到替代结点,如果替代结点是黑色,递归向上依次判断侄子结点、父结点是否可以补充被删除的黑色,整体思想就是将删除一个黑色结点造成的影响局限在子树内处理。


小结

当然实现过程中调试也占了很大一部分,我使用了两项方法帮助调试:

  • 由于插入多个结点时,无法确定是处理哪个结点时出了问题,于是我给红黑树类添加了 debug 属性,用二分法设置此属性来找到问题结点;
  • 给红黑树类添加了 printTree() 方法,实时打印树结构,确定代码问题再分析;

由于红黑树相对其他树实在较为复杂,只通过思考就完全理解不太现实,还需要自己去试着画,试着实现,我画了 5 张 A4 纸的正反面才算理解了红黑树,即便如此,在写这篇文章时还发现了代码中的可优化点。

而且代码实现比画图还略复杂,理论中的一个旋转就包含了 左旋/右旋/先左旋再右旋/先右旋再左旋 几种情况,虽然有一定规律,还是自己实现一下印象最深刻。

关于本文有什么问题可以在下面留言交流,如果您觉得本文对您有帮助,可以点击下面的 推荐 支持一下我,博客一直在更新,欢迎 关注 。

原文地址:https://www.cnblogs.com/dudadi/p/8185695.html

时间: 2024-08-29 16:31:24

通过2-3-4树理解红黑树的相关文章

通过2-3树理解红黑树

一.简介 前面的文章我们循序渐进的讲解了<二叉树><二分搜索树><AVL-平衡二叉树>,从左至右互为基础.尤其是二分搜索树给了我们如何将数据组织成为搜索树的思想,当然二分搜索树存在的天然问题--在极端情况下回退化为链表.所以引出了AVL-平衡二叉树,通过再平衡即LL,LR,RR,RL四个旋转操作维护了一棵平衡的二分搜索树.本章节我们继续梳理一个高阶的树结构即:红黑树.想必大家都知道,红黑树如何维持平衡,如何进行颜色反转让人很难理解,虽然很多博文很多书对红黑树都有讲解,但

浅谈算法和数据结构: 七 二叉查找树 八 平衡查找树之2-3树 九 平衡查找树之红黑树 十 平衡查找树之B树

http://www.cnblogs.com/yangecnu/p/Introduce-Binary-Search-Tree.html 前文介绍了符号表的两种实现,无序链表和有序数组,无序链表在插入的时候具有较高的灵活性,而有序数组在查找时具有较高的效率,本文介绍的二叉查找树(Binary Search Tree,BST)这一数据结构综合了以上两种数据结构的优点. 二叉查找树具有很高的灵活性,对其优化可以生成平衡二叉树,红黑树等高效的查找和插入数据结构,后文会一一介绍. 一 定义 二叉查找树(B

AVL树,红黑树,B-B+树,Trie树原理和应用

前言:本文章来源于我在知乎上回答的一个问题 AVL树,红黑树,B树,B+树,Trie树都分别应用在哪些现实场景中? 看完后您可能会了解到这些数据结构大致的原理及为什么用在这些场景,文章并不涉及具体操作(如插入删除等等) 目录 AVL树 AVL树原理与应用 红黑树 红黑树原理与应用 B/B+树 B/B+树原理与应用 Trie树 Trie树原理与应用 AVL树 简介: AVL树是最早的自平衡二叉树,在早期应用还相对来说比较广,后期由于旋转次数过多而被红黑树等结构取代(二者都是用来搜索的),AVL树内

浅谈算法和数据结构: 九 平衡查找树之红黑树

原文:浅谈算法和数据结构: 九 平衡查找树之红黑树 前面一篇文章介绍了2-3查找树,可以看到,2-3查找树能保证在插入元素之后能保持树的平衡状态,最坏情况下即所有的子节点都是2-node,树的高度为lgN,从而保证了最坏情况下的时间复杂度.但是2-3树实现起来比较复杂,本文介绍一种简单实现2-3树的数据结构,即红黑树(Red-Black Tree) 定义 红黑树的主要是像是对2-3查找树进行编码,尤其是对2-3查找树中的3-nodes节点添加额外的信息.红黑树中将节点之间的链接分为两种不同类型,

B树、B+树、红黑树、AVL树

定义及概念 B树 二叉树的深度较大,在查找时会造成I/O读写频繁,查询效率低下,所以引入了多叉树的结构,也就是B树.阶为M的B树具有以下性质: 1.根节点在不为叶子节点的情况下儿子数为 2 ~ M2.除根结点以外的非叶子结点的儿子数为 M/2(向上取整) ~ M3.拥有 K 个孩子的非叶子节点包含 k-1 个keys(关键字),且递增排列4.所有叶子结点在同一层,即深度相同 (叶节点可以看成是一种外部节点,不包含任何关键字信息) 在B-树中,每个结点中关键字从小到大排列,并且当该结点的孩子是非叶

算法-树(2)—2-3树,红黑树

本篇文章主要介绍2-3树,并由2-3树重点介绍RB树(红黑树) 后附完整代码 2-3树 1. 2-3树 2-3树概念: 一颗2-3查找树,或为空树,或为由2-结点,3-结点构成的树. 2-结点:含有一个键值对和两个链接,左链接的结点均小于该结点,右链接的结点均大于该结点. 3-结点:含有两个键值对和三个链接,左链接的结点小于该节点的小的键值,中链接介于该结点的两个键值之间,右链接大于改结点的大的键值. 2. 2-3树的查找添加 1).查找: 参照上一篇文章,实现较为简单,即比较需要查找的key值

结合java.util.TreeMap源码理解红黑树

前言 本篇将结合JDK1.6的TreeMap源码,来一起探索红-黑树的奥秘.红黑树是解决二叉搜索树的非平衡问题. 当插入(或者删除)一个新节点时,为了使树保持平衡,必须遵循一定的规则,这个规则就是红-黑规则: 1) 每个节点不是红色的就是黑色的 2) 根总是黑色的 3) 如果节点是红色的,则它的子节点必须是黑色的(反之倒不一定必须为真) 4) 从跟到叶节点或者空子节点的每条路径,必须包含相同数目的黑色节点 插入一个新节点 红-黑树的插入过程和普通的二叉搜索树基本一致:从跟朝插入点位置走,在每个节

高级树、AVL 树和红黑树

高级树.AVL 树和红黑树 二叉树遍历 Pre-order/In-order/Post-orde 前序(Pre-order):根-左-右 中序(In-order):左-根-右 后序(Post-order):左-右-根 示例代码 def preorder(self, root): if root: self .traverse_path.append(root.val) self .preorder(root.left) self .preorder(root.right) def inorder

查找(二):彻底理解红黑树和平衡查找树

平衡查找树 在之前的二分搜索和二叉查找树中已经能够很好地解决查找的问题了,但是它们在最坏情况下的性能还是很糟糕,我们可以在查找二叉树中,每次动态插入或删除某结点时,都重新构造为完全二叉树,但是这样代价太大,所以就引出了平衡查找树. 详细的数学定义就不给出了,因为既不直观也记不住,直接给出一个平衡二叉树的图: 相信这个图一看就明白了,平衡查找树(以下简称BST或2-3查找树),下面给出一些基本的定义: 一棵2-3查找树或为一棵空树,或由一下结点组成: 2-结点,含有一个键(及其对应的值)和两条链接