红黑树(Red Black Tree)是一种自平衡的二叉搜索树(Self-balancing Binary Search Tree)。以前也叫做平衡二叉 B 树(Symmetric Binary B-tree)。

预备知识

树的知识框架结构如下图所示:

平衡二叉搜索树

平衡二叉搜索树(Balanced Binary Search Tree),英文简称 BBST。经典常见的平衡二叉搜索树是 AVL 树和红黑树。

二叉搜索树

二叉搜索树(Binary Search Tree)是二叉树的一种,英文简称 BST。又称为二叉查找树、二叉排序树。

它的特点是任何一个结点的值都大于其左子树的所有结点的值,任何一个结点的值都小于其右子树的所有结点的值。

平衡

平衡(Balance):就是当结点数量固定时,左右子树的高度越接近,这棵二叉树越平衡(高度越低)。而最理想的平衡就是完全二叉树/满二叉树,高度最小的二叉树。

一棵二叉搜索树平均时间复杂度可以认为是树的高度 O(h)。像左边这棵,结点的左右子树的高度接近,属于一棵平衡二叉搜索树,O(h) = O(logn);而右边这棵,高度达到了最大,已经退化成了链表,O(h)=O(n)。

改进二叉搜索树

当二叉树退化成链表时,性能是很低的,所以我们需要在结点的插入、删除操作之后,想办法让二叉搜索树恢复平衡(减小树的高度)。但是如果为了追求最理想的平衡,而增加了时间复杂度也不是很有必要,因此比较合理的方案就是:用尽量少的调整次数达到适度平衡。

由此引申出 AVL 树的概念

AVL 树

AVL 树是最早发明的自平衡二叉搜索树之一,它取名自两位发明家的名字:G.M.Adelson-Velsky 和 E.M.Landis。

平衡因子

平衡因子(Balance Factor):某结点的左右子树的高度差。

每个叶子结点的平衡因子都是 0。看这棵二叉搜索树,红色数字标注了每个结点对应的平衡因子。

举例:

8 的左子树高度为 2,右子树高度为 1,因此它的平衡因子为 1;5 的左子树高度为 0,右子树高度为 3,因此它的平衡因子为 -3;4 的左子树高度为 2,右子树高度为 4,因此它的平衡因子为 -2;

再看这棵 AVL 树和它每个结点对应的平衡因子:

可以看到 AVL 树具有以下特点:每个结点的平衡因子只可能是 -1、0、1(如果绝对值超过 1,则认为是失衡)

每个结点的左右子树高度差不超过 1

搜索、插入、删除的时间复杂度是 O(logn)

B树

B 树(Balanced Tree)是一种平衡的多路搜索树,多用于文件系统、数据库的实现。这是一个简单的 3 阶 B 树:

特点1 个结点可以存储超过 2 个元素,可以拥有超过 2 个子结点

拥有二叉搜索树的一些性质

平衡,每个结点的所有子树高度一致

比较矮

m 阶 B 树的性质(m ≥ 2)

m 阶 B 树指的是一个结点最多拥有 m 个子结点。假设一个结点存储的元素个数为 x,那么如果这个结点是:根结点:1 ≤ x ≤ m - 1

非根结点:┌ m / 2 ┐ - 1 ≤ x ≤ m - 1

如果有子结点,子结点个数为 y = x + 1,那么如果这个结点是:根结点:2 ≤ y ≤ m

非根结点:┌ m / 2 ┐ ≤ y ≤ m向上取整(Ceiling),指的是取比自己大的最小整数,用数学符号 ┌ ┐ 表示。 向下取整(Floor),指的是取比自己小的最大整数,用数学符号 └ ┘ 表示。

比如 m = 3, 子结点个数 2 ≤ y ≤ 3,这个 B 树可以称为(2,3)树、2-3 树;

比如 m = 4, 子结点个数 2 ≤ y ≤ 4,这个 B 树可以称为(2,4)树、2-3-4 树;

比如 m = 5, 子结点个数 3 ≤ y ≤ 4,这个 B 树可以称为(3,5)树、3-4-5 树;

以此类推。

B 树 VS 二叉搜索树

这是一棵二叉搜索树,通过某些父子结点合并,恰好能与上面的 B 树对应。我们可以得到结论:B 树和二叉搜索树,在逻辑上是等价的

多代结点合并,可以获得一个超级结点,且 n 代合并的超级结点,最多拥有 (2^n) 个子结点 (至少是 (2^n) 阶 B 树)

红黑树定义和性质

红黑树是一种含有红黑结点并能自平衡的二叉搜索树。

为了保证平衡,红黑树必须满足以下性质:每个结点是要么是红色或黑色

根结点必须是黑色

叶结点(外部结点、空结点)是黑色

红色结点不能连续(也就是,红色结点的孩子和父亲都是黑色)

对于每个结点,从该点至 nil(树尾端,java 中为 null 的结点)的任何路径都包含所相同个数的黑色结点

红黑树与 B 树的等价变换

根据上面的性质,可以画出这样一棵红黑树。接下来对红黑树做等价变换,即将所有的红色结点上升一层与它的父结点放在同一行,这就很像一棵 4 阶 B 树,转换效果如下图所示。

可以得出结论:红黑树与 4 阶 B 树(2-3-4树)具有等价性

黑色结点与红色子结点融合在一起,形成 1 个 B 树结点

红黑树的黑色结点个数与 4 阶 B 树的结点总个数相等

红黑树的基本操作

当我们对一棵平衡二叉搜索树进行插入、删除的时候,很可能会让这棵树变得失衡(最坏可能导致所有祖先结点失衡,但是父结点和非祖先结点都不可能失衡),为了达到平衡,需要对树进行旋转。而红黑树能够达到自平衡,靠的也就是左旋、右旋和变色。

旋转操作是局部的。当一侧子树的结点少了,向另一侧“借”一些结点;当一侧子树的结点多了,则“租”一些结点给另一侧。

为了更清楚地讲解这部分内容,先声明几个概念:

N - node:当前结点

P - parent:父结点

S - sibling:兄弟结点

U - uncle:叔父结点(P 的兄弟结点)

G - grand:祖父结点(P 的父结点)

左旋

左旋指的是以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父结点,右子结点的左子结点变为旋转结点的右子结点,左子结点保持不变。

不考虑结点颜色,可以看到左旋只影响旋转结点和其右子树的结构,把右子树的结点往左子树移动。

右旋

右旋指的是以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父结点,左子结点的右子结点变为旋转结点的左子结点,右子结点保持不变。

不考虑结点颜色,可以看到右旋只影响旋转结点和其左子树的结构,把左子树的结点往右子树移动。

变色

变色指的是结点的颜色由红变黑或由黑变红

变换规则

将左旋、右旋和变色结合起来,得到一套变换规则:

变色:如果当前结点的父结点和叔父结点是红色,那么:把父结点和叔父结点变为黑色

把祖父结点变为红色

把指针定义到祖父结点

左旋:当前结点是右子树,且父结点是红色,叔父结点是黑色,对它的父结点左旋

右旋:当前结点是左子树,且父结点是红色,叔父结点是黑色,那么:把父结点变为黑色

把祖父结点变为红色

对祖父结点右旋

红黑树搜索

由于红黑树本来就是平衡二叉搜索树,并且搜索也不会破坏树的平衡,所以搜索算法也与平衡二叉搜索树一致:

具体步骤:从根结点开始检索,把根结点设置为当前结点;

若当前结点为空,返回 nil。

若当前结点不为空,比较当前结点 key 与搜索 key 的大小;

若当前结点 key 等于搜索 key,那么该 key 就是搜索目标,返回当前结点。

若当前结点 key 大于搜索 key,把当前结点的左子结点设置为当前结点,重复步骤 2;

若当前结点 key 小于搜索 key,把当前结点的右子结点设置为当前结点,重复步骤 2;

红黑树插入

红黑树插入操作分为下面两步:

定位插入的位置

具体步骤:从根结点开始检索;

若根结点为空,那么插入结点设为根结点,结束。

若根结点不为空,那么把根结点设为当前结点;

若当前结点为 nil,返回当前结点的父结点,结束。

若当前结点 key 等于搜索 key,那么该 key 所在结点就是插入结点,更新结点的值,结束。

若当前结点 key 大于搜索 key,把当前结点的左子结点设置为当前结点,重复步骤4;

若当前结点 key 小于搜索 key,把当前结点的右子结点设置为当前结点,重复步骤4;

插入后实现自平衡

建议新添加的结点默认为红色,因此这样能够让红黑树的性质尽快满足。不过如果添加的结点是根结点,设为黑色即可。

总结一下红黑树插入可能出现的所有场景。

场景 1:红黑树为空树

红黑树的性质2:根结点必须是黑色。

处理:直接把插入结点设成黑色并作为根结点。

场景 2:插入结点的 key 已存在

二叉搜索树中不能插入相同元素,既然结点的 key 已经存在,红黑树也已平衡,无需重复插入。

处理:将插入结点设为将要替换结点的颜色

更新当前结点的值为插入结点的值

场景 3:插入结点的父结点为黑色

插入的结点默认是红色的,当它的父结点是黑色时,并不会破坏平衡。

处理:直接插入。

场景 4:插入结点的父结点为红色

如果插入结点的父结点为红色,那么父结点不可能为根结点,所以插入结点总是存在祖父结点。这点很重要,后续的旋转操作需要祖父结点的参与。

场景 4.1:存在叔父结点,且为红色

由红黑树性质4可知:红色结点不能连续。那么此时该插入子树的红黑层数的情况是:黑-红-红。显然最简单的处理方式就是将其改为:红-黑-红。

处理:将父结点和叔父结点变为黑色

将祖父结点变为红色

将祖父结点设置为当前插入结点

场景 4.2:叔父结点不存在或为黑色,插入结点的父结点是祖父结点的左子结点

这种场景下,叔父结点所在的子树的黑色结点就比父结点所在子树的多,不满足红黑树的性质5。

场景 4.2.1:插入结点是左子树

处理:将父结点变为黑色

将祖父结点变为红色

将祖父结点右旋

场景 4.2.2:插入结点是左子树

这种场景显然可以转换为 4.2.1。

处理:将父结点进行左旋

将父结点设为插入结点,得到场景 4.2.1

进行场景 4.2.1 的处理

场景4.3:叔父结点不存在或为黑色,插入结点的父结点是祖父结点的右子结点

相当于场景 4.2 的方向反转,直接看图。

场景 4.3.1:插入结点是左子树

处理:将父结点变为黑色

将祖父结点变为红色

对祖父结点进行左旋

场景 4.3.2:插入结点是右子树

处理:将父结点进行右旋

将父结点设置为插入结点,得到场景 4.3.1

进行场景 4.3.1 的处理

下面举个例子,往一棵红黑树中插入元素,整棵树的变换如下图所示:

红黑树删除

红黑树删除操作也分为两步:

定位删除的位置

定位删除位置可以复用红黑树搜索的操作。

如果不存在目标结点,忽略本次操作;如果找到目标结点,删除后进行自平衡处理。

删除后实现自平衡

二叉搜索树删除的时候可能出现三种场景:场景一:若删除结点无子结点,直接删除即可;

场景二:若删除结点只有一个子结点,用子结点替换删除结点;

场景三:若删除结点有两个子结点,用**后继结点(大于删除结点的最小结点)**替换删除结点。

具体应用,可以借助这张图理解:

我们可以发现,另外两种二叉树的删除场景都可以通过相互转换变为场景一。

在场景二情况下:删除结点用其唯一的子结点替换,子结点替换为删除结点后,可以认为删除的是子结点,若子结点又有两个子结点,那么相当于转换为场景三,一直自顶向下转换,总是能转换为场景一。

在场景三情况下:删除结点用后继结点,如果后继结点有右子结点,那么相当于转换为场景二,否则转为场景一。

综上所述,删除的结点可以看作删除替换结点,且替换结点最后总是在树末。

下面总结一下红黑树删除可能出现的所有场景。

为了方面理解,我们先约定一下结点的叫法:

R - 替换结点

P - 替换结点的父结点

S - 替换结点的兄弟结点

SL - 兄弟结点的左子结点

SR - 兄弟结点的右子结点

灰色 - 结点颜色可能是红色,也可能是黑色

注意:R 是即将被替换到删除结点的位置的替换结点,在删除前,它还在原来所在位置参与树的子平衡,平衡后再替换到删除结点的位置,才算删除完成。

场景 1:替换结点为红色

我们把替换结点换到了删除结点的位置时,由于替换结点为红色,删除也了不会影响红黑树的平衡,只要把替换结点的颜色变为删除的结点的颜色即可重新平衡。

处理:替换结点颜色变为删除结点的颜色。

场景 2:替换结点为黑色

当替换结点是黑色时,就必须进行自平衡处理了,我们可以通过区分替换结点是其父结点的左子结点还是右子结点,来做不同的旋转,使树重新平衡。

场景 2.1:替换结点是左子树

场景 2.1.1:替换结点的兄弟结点为红色

若兄弟结点是红结点,那么根据红黑树性质4,兄弟结点的父结点和子结点肯定为黑色,按照下图方式处理,得到删除场景 2.1.2.3。

处理:将兄弟结点变为黑色

将父结点变为红色

对父结点进行左旋,得到场景 2.1.2.3

进行场景 2.1.2.3 的处理

场景 2.1.2:替换结点的兄弟结点为黑色

当兄弟结点为黑时,其父结点和子结点的具体颜色也无法确定,此时又得考虑多种子场景

场景 2.1.2.1:替换结点的兄弟结点的右子结点为红色,左子结点任意颜色

即将删除的左子树的一个黑色结点,显然左子树的黑色结点少1了,然而右子结点又是红色,那么我们直接向右子树“借”个红结点来补充黑结点,并进行旋转处理。如图所示:

处理:将兄弟结点的颜色变为父结点的颜色

将父结点变为黑色

将兄弟结点的右子结点变为黑色

对父结点进行左旋

场景 2.1.2.2:替换结点的兄弟结点的右子结点为黑色,左子结点为红色

兄弟结点所在的子树有红结点,又可以向兄弟子树“借”个红结点过来,这就转换回了场景 2.1.2.1。如图所示:

处理:将兄弟结点变为红色

将兄弟结点的左子结点变为黑色

对兄弟结点进行右旋,得到场景 2.1.2.1

进行场景 2.1.2.1 的处理

场景 2.1.2.3:替换结点的兄弟结点的子结点都为黑色

兄弟子树没有红结点可以“借”了,再向父结点“借”。如果父结点是黑色,为了让父结点在所在的子树中保证平衡(替换结点即将删除,少了一个黑色结点,子树也需要少一个)先把兄弟结点变为红色,再让父结点成为新的替换结点。

处理:如果父结点为黑色将兄弟结点变为红色

将父结点作为新的替换结点

重新进行删除结点的场景处理

如果父结点为红色替换结点的父结点和替换结点的兄弟结点颜色交换

删除结点和替换结点的值交换后,删除替换结点

场景 2.2:替换结点是右子树

实际上是场景 2.1 的镜像操作。

场景 2.2.1:替换结点的兄弟结点为红色

处理:将兄弟结点变为黑色

将父结点变为红色

对父结点进行右旋,得到场景 2.2.2.3

进行场景 2.2.2.3 的处理

场景 2.2.2:替换结点的兄弟结点为黑色

场景 2.2.2.1:替换结点的兄弟结点的左子结点为红色,右子结点任意颜色

处理

处理:将兄弟结点的颜色变为父结点的颜色

将父结点变为黑色

将兄弟结点的左子结点变为黑色

对父结点进行右旋

场景 2.2.2.2:替换结点的兄弟结点的左子结点为黑色,右子结点为红色

处理:将兄弟结点变为红色

将兄弟结点的右子结点设为黑色

对兄弟结点进行左旋,得到场景 2.2.2.1

进行场景 2.2.2.1 的处理

场景 2.2.2.3:替换结点的兄弟结点的子结点都为黑色

处理:如果父结点为黑色将兄弟结点变为红色

将父结点作为新的替换结点

重新进行删除结点的场景处理

如果父结点为红色替换结点的父结点和替换结点的兄弟结点颜色交换

删除结点和替换结点的值交换后,删除替换结点

最后

在学习过程中,我喜欢找的- -些电子书来系统化的学习,多数时候我都只是做一一个大概的解,没有深入学习,基本就是看下目录,看下前几章的内容,深入的只会在实际用的时候才去详细看了。码邦主:程序员必备 Java 核心知识点整理​zhuanlan.zhihu.com码邦主:1000道Java工程师面试题+答案PDF485页​zhuanlan.zhihu.com

包括Java基础、Web前后端、各种框架、数据库、架构设计、目管理、操作系统以及IT行业等类型的书籍。作者:FiTeen

链接:【程序员面试必备】红黑树详细图解

来源:掘金

红黑树模拟软件_【程序员面试必备】红黑树详细图解相关推荐

  1. 金三银四求职季,程序员面试必备——编程语言篇

    每年的三四月都是准毕业生最焦灼最忙碌的季节,考研的结果基本都已确定,公务员考试也基本落下帷幕,春招将会陆续进行,很多同学也要开始决定未来工作的方向.每年的三四月份也都是互联网人的跳槽高峰期,所以三四月 ...

  2. Python程序员面试必备常用问题答案及解析

    源 | 小象     文 | 水木华章 在发布<Python程序员面试,这些问题你必须提前准备!>一文后,应广大程序员朋友的强烈要求,小编就Python程序员面试必备问题整理了一份参考答案 ...

  3. 金三银四求职季,程序员面试必备——数据库篇

    我们身处一个技术变革的时代,随着分布式.云计算.大数据等技术的飞速发展,各种数据库百花齐放.它们在具体功能和使用场景上各有千秋,在实现细节上也不尽相同,但是在工作原理上又有着诸多共通性和相似性. 跳槽 ...

  4. 金三银四求职季,程序员面试必备——数据分析篇

    这个年代里,"用数据说话"已经像是一种过气的口号.各行各业不同角色和身份的人们都已懂得"用数据说话"的重要性,甚至日常生活中也需要用数据看清事实,科学吃瓜.所以 ...

  5. python程序员面试自我介绍_程序员面试经验总结

    主要包括以下内容: 一.程序员面试过程中,面试官想要从 " 自我介绍 " 获得什么信息? 二.如何去提炼简历中的精华?如何在沟通中体现你的软技能? 三.如何去解释简历中的瑕疵? 四 ...

  6. 程序员面试必备——《Java程序员面试笔试宝典》pdf

    <Java程序员面试笔试宝典>pdf 链接: https://pan.baidu.com/s/1uqxvY55jFOEAAzcn-pNVSQ 提取码: fety 1 内容简介 · · · ...

  7. python编写抢座位软件_程序员硬核Python抢票教程”,帮你抢回家车票

    盼望着,盼望着,春节的脚步近了,然而,每年到这个时候,最难的,莫过于一张回家的火车票. 据悉,今年春运期间,全国铁路发送旅客人次同比将增长8.0%,达到4.4亿人次,2020年铁路春运自1月10日开始 ...

  8. python开发pc软件_程序员带你十天快速入门Python,玩转电脑软件开发(二)

    关注今日头条-做全栈攻城狮,学代码也要读书,爱全栈,更爱生活.提供程序员技术及生活指导干货. 如果你真想学习,请评论学过的每篇文章,记录学习的痕迹. 请把所有教程文章中所提及的代码,最少敲写三遍,达到 ...

  9. ios查看帧率的软件_程序员必看!直播软件开发弱网下保障高清流畅推流的方法...

    通常情况下程序员在开发直播软件时,优化卡顿和延迟是比较多的,只要是优化欠佳,就会导致前端APP运行出问题,为了帮助程序员在开发过程中能更加合理有效的优化,小编在这里从开发的层面简要介绍一下影响直播体验 ...

最新文章

  1. CodeForces - 1400D Zigzags(简单dp)
  2. jmeter的分布式部署
  3. Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.4.4 延迟初始化的bean...
  4. 听 Fabien Potencier 谈Symfony2 之 《What is Symfony2 ?》
  5. BigInteger类详解
  6. oracle批量更新数据从另一表_全市场期货数据的批量下载和更新
  7. 25个深度学习开源数据集
  8. SQL Server Update:使用 TOP 限制更新的数据
  9. 【转载】著名黑客雷蒙评价几种编程语言
  10. Python五子棋游戏源代码源程序
  11. 基于MATLAB的身份证号码的识别算法
  12. 30岁后的测试工程师都去哪儿了?
  13. 【Proteus仿真】Arduino UNO利用Stepper库实现uln2003驱动步进电机转动
  14. 泛泰长短信修改教程(供其他泛泰机型参考)
  15. C语言关于输入某天日期求是本年第几天(计算天数)
  16. Mac电池显示需要维修
  17. LTE-A载波聚合技术(14)---PHICH
  18. 谷歌地图的简单轨迹移动播放
  19. 牛客网项目--MyBatis
  20. 新浪微博桌面客户端 v3.0.5.35466 官方PC版

热门文章

  1. eclipse和mysql创建ssm_Eclipse下SSM项目的搭建
  2. java做爬虫和python做爬虫_为什么常用Python,Java做爬虫,而不是C#C++等?
  3. echarts折现变曲线_echarts将折线图改为曲线图
  4. C++STL笔记(八):set和multiset详解
  5. html帮助文件的制作软件,FAR HTML(帮助文件制作软件)V5.6.1 官方特别版
  6. fragment photoshop_史上最接地气的Photoshop?谈PS 2021的黑科技 - Adobe
  7. myftpadmin+proftpd+mysql架设ftp服务器_[教程]在CentOS7上配置 FTP服务器 Proftpd 支持 MySQL 虚拟用户加密认证以及磁盘限额(Quota)...
  8. gcc编译的几个重要参数
  9. 大厂程序员年薪_年薪25万一线大厂程序员,年薪10万三线城市大专讲师,怎么选...
  10. rsyslog数据流分析