数据结构 - 学习笔记 - 红黑树

  • 定义
  • 简介
  • 知识点
    • 1. 结点属性
    • 2. 前驱、后继
    • 3. 旋转
  • 查找
  • 插入
    • 父结点为黑色
    • 父结点为红色
    • 1. 有4种情形只需要变色(对应234树4结点)
      • 1.1. 变色实现平衡
      • 1.2. 递归调整颜色
    • 2. 有4种情形需要旋转 + 变色(对应234树3结点)
  • 删除
    • 删除无子结点的黑色结点
      • 1. 兄弟为红色
        • 1.1. 找真兄弟(转换的另一种说法)
      • 2. 兄弟为**黑色**
        • 2.1. 兄弟有红色子节点
        • 2.2. 兄弟无红色子节点
          • 2.2.1. 父结点为红色
          • 2.2.2. 父结点为**黑色**
    • 时间复杂度
  • 辅助脚本
  • 参考资料

定义

  1. 所有结点非 。(插入新结点默认色,然后再按需调整)
  2. 根结点 必需是 色。
  3. 叶结点 必需是 色。(叶结点是指最末端的空结点。通常在代码中直接用 null 表示,不会创建实际结点。)
  4. 结点的子结点必需是 。有两棵子树。(不能有连续的两个结点)
  5. 任意结点叶节点 经过的 结点数量相同。(别忘记叶结点也是黑色结点 )

简介

  1. 红黑树234树的一种实现,所以学习红黑树前,先看 红黑树前传——234树。
  2. 它是一种自平衡二叉查找树
    2.1. 二叉查找树左子树所有结点值都 <= 父,右子树所有结点值都 >= 父。
    2.2. 左右子树也都是二叉查找树
    2.3. 二叉查找树存在不平衡的情况,极端情况就是个链表
  3. AVL(平衡二叉查找树)相比,适当舍弃平衡,换取插入删除性能提升的同时,兼顾了查找效率。(本质上就是引入了红色结点的概念,它作为填充物,不计入树高,可有可无。因此简少了调平衡的工作量。)
  4. 因此红黑树中保障的是 结点的平衡, 结点作为维持平衡的填充物不影响平衡。
  5. 路径可以全是 结点。如:
  6. 路径可以两 夹一 。如:
  7. 插入删除打破平衡时,通过变色旋转实现再平衡。

知识点

1. 结点属性

颜色、键、左子结点、右子结点、父结点(特殊情况:结点没有结点)

2. 前驱、后继

  • 前驱结点:从子树中找最大的结点
  • 后继结点:从子树中找最小的结点

如果我们中序遍历一次,得到从小到大排列的所有元素,就能很直观的看到前驱后继就是当前结点身边一前一后的两位,删除当前结点,用它们其中一个补位,是最合适的了。

中序遍历结果:[2, 3, 4, 5, 6, 7, 8]
当前结点:5
前驱:4     后继:6

3. 旋转

  • 旋转操作不会改变树中序遍历的顺序。
  • 旋转操作通过挪东墙补西墙来维护二叉树的平衡。
(对E进行)左旋 (对S进行)右旋
  • 总结一下几个结点的规律:
结点 缩写 左旋规律 右旋规律
E E E.parent 更新为S
E.right更新为S.left
E.right 更新为 S
E.parent 更新为 S.parent
S S S.left 更新为 E
S.parent 更新为 E.parent
S.parent 更新为E
S.left更新为E.right
between E and S BES BES.parent 更新为 E BES.parent 更新为 S
less than E LE 不受任何影响 不受任何影响
greater than S GS 不受任何影响 不受任何影响
当前子树的父结点 P 如果当前是左子树,更新 P.left 如果当前是右子树,更新 P.right
  • 伪代码
// 左旋
S = E.right;
BES = S.left;
E.right = BES;
BES.parent = E;
S.parent = E.parent; // E.parent == S.parent.parent
if(S.parent == null){ // S没爹,自己就是根S.颜色 = 黑色
}else if(E.parent.left == E){ // E 是父的左子E.parent.left = S;
}else{                        // E 是父的右子E.parent.right = S;
}
S.left = E;
E.parent = S;

查找

  1. 从根结点开始。
  2. 对比结点与查找目标,
    2.1. 目标值 = 当前结点:成功找到目标。
    2.2. 目标值 < 当前结点:递归查找左子树
    2.3. 目标值 > 当前结点:递归查找右子树

插入

红黑树插入共12种情况:父4种,父8种。

  1. 插入的新结点,默认都是红色结点。
  2. 插入位置的父结点为黑色。不影响平衡,共 4 种情况。直接插入即可。
    2.1. 父结点无子结点,插入左子结点。
    2.2. 父结点无子结点,插入右子结点。
    2.3. 父结点有红色右子,插入左子
    2.4. 父结点有红色左子,插入右子
  3. 插入位置的父结点为红色
    3.1. 对应234树4结点有4种:祖父黑色,父叔伯都是红色。只需变色实现平衡。
    3.2. 对应234树3结点有4种: LL、LR、RR、RL。需要旋转 + 变色实现平衡。

父结点为黑色

直接插入即可。

父结点为红色

1. 有4种情形只需要变色(对应234树4结点)

结点情况结点为红色且红色叔伯结点。

1.1. 变色实现平衡

  1. 【红黑树】 插入新结点
  2. 【红黑树】 结点、叔伯结点变成黑色祖父结点变成红色
    2.1. 结点与祖父结点调换颜色:满足红结点子必的定义。同时对于插入新结点的这一路径来说黑结点数未发生变化。(相当于把祖父的黑色借来用了)
    2.2. 叔伯结点变成黑色祖父结点原本作为公共的黑结点,挪给另一侧后,当前这条路径就少了一个黑结点。因此 叔伯要站出来变维持平衡。
  3. 【红黑树】 最后祖父结点更新为当前结点。
    3.1. 如果是根结点,直接染黑
    3.2. 如果是红色,则需要向上递归调整颜色,一直到根。

1.2. 递归调整颜色

插入新结点 后递归触发变色。
触发递归的原因曾祖结点是红色祖父结点染红后出现两个连续红色结点的情况,需要以祖父为当前结点继续进行变色处理。最终到达根结点直接染黑结束。

2. 有4种情形需要旋转 + 变色(对应234树3结点)

结点情况结点为红色且红色叔伯结点。

虽然共有4种 情形,但其中 LR可以转为LLRL可以转为RR。总之就是有拐弯的,都是转为一条直线,再处理。
1.1. LR 左旋1次,转为 LL
1.2. LL 右旋1次 + 染色,实现平衡。
2.1. RL 右旋1次,转为 LL
2.2. RR 左旋1次 + 染色,实现平衡。

删除

删除可能发生在树中的任意位置。结点删除后,可能影响树的平衡,需要重新调整实现平衡。

根据将被删除的目标结点颜色子结点数量,需要分别处理:

- 当前红色 当前黑色
有两个子结点 同黑色 1. 使用前驱后继结点内容替换当前结点。(颜色保持不变)
2. 再删除前驱后继结点。(转变成了对:无子结点的删除)
有一个子结点 直接删除 1. 删除当前结点。
2. 使用子结点填补当前位置,并将颜色设置为黑色
无子结点 直接删除 1. 当前为根:直接删除。
2. 兄弟为红色:转为黑色再继续处理。
3. 兄弟为黑色:按兄弟有无红色子节点,分别处理。详情如下:

从上面这个表中可以看出只有删除无子结点黑色结点这一种情况需要我们详细讨论,那么接下来就开始吧:

删除无子结点的黑色结点

1. 兄弟为红色

兄弟为红色:兄弟转为黑色再继续处理。转换方法:

  1. 兄弟结点调整为黑色
  2. 结点调整为红色
  3. 当前结点的结点,进行右旋

1.1. 找真兄弟(转换的另一种说法)

红色兄弟转黑兄弟。也可以理解为:只有黑色才是真兄弟,红色是塑料兄弟。所以我们要找到真兄弟再干活。

  1. 当前是子结点,就获取父亲子结点,判断颜色。如果是黑色,找到兄弟,完成任务。
  2. 结果发现是红色,交换颜色。(不存在连续红,所以父必然是个黑结点)
  3. 父结点为支点,左旋 一次。
  4. 现在取父结点右子结点,就是真兄弟了。(就是兄弟左子结点)

如果当前是子。同理往左边找就行了。

2. 兄弟为黑色

黑色是真兄弟,可算见到亲人了。可以开始真正的删除操作了。

2.1. 兄弟有红色子节点

借用兄弟子结点修复平衡。

2.2. 兄弟无红色子节点

借助父结点来修复平衡

2.2.1. 父结点为红色
  1. 删除当前结点。
  2. 兄弟结点调整为红色
  3. 结点调整为黑色

2.2.2. 父结点为黑色

  1. 删除当前结点 ①。
  2. 兄弟结点 调整为红色
  3. 结点 ② 作为当前结点,继续处理:
    3.1. 兄弟结点 ⑥ 调整为红色
    3.2. 判断如果结点 ④ 为红色把 ④ 染
    3.3. 否则更新 ④ 为 当前结点 递归处理。直到遇到红色父结点并将其染黑实现平衡,或到达根结点为止。

时间复杂度

操作 复杂度
查找 O(lgn)
插入 O(lgn)
删除 O(lgn)

辅助脚本

红黑树可视化演示

var sleep = (delaytime = 1000) => {return new Promise(resolve => setTimeout(resolve, delaytime))
}
async function delayDo(arr, callback = data=>console.log(`数据:${data}`), delaytime) {var len = arr.length;for (let i = 0; i <len  ; i++) {        await sleep(delaytime);callback(arr[i]);}c
};
// 获取文本框
var [insertTxt, deleteTxt, findTxt] = [...document.querySelectorAll("#AlgorithmSpecificControls [type=Text]")];
// 获取按钮
var [insertBtn, deleteBtn, findBtn] = [...document.querySelectorAll("#AlgorithmSpecificControls [type=Button]")];
var process = {insert: function insert(v){ insertTxt.value = v; insertBtn.click();    },del: function del(v){ deleteTxt.value = v; deleteBtn.click(); },find: function find(v){ findTxt.value = v; findBtn.click(); }
}
// 遍历数组,间隔 n 秒处理一个元素。
function main(arr = [...Array(10).keys()], cb = v=>console.log(v), delaytime=200){delaytime = delaytime<200 ? 200 : delaytimedelayDo(arr, v => cb(v), delaytime);
}
// 插入元素,间隔 200 毫秒
main([...Array(20).keys()].map(v=>v+1), process.insert, 800);

参考资料

笑虾:数据结构 - 学习笔记 - 红黑树前传——234树

www.cs.usfca.edu:Red/Black Tree 数据结构可视化
Programiz:Red-Black Tree
Algorithmtutor:Red Black Trees (with implementation in C++, Java, and Python)

数据结构 - 学习笔记 - 红黑树相关推荐

  1. python 红黑树_python学习笔记|红黑树(性质与插入)

    定义 一种含有红黑节点并能自平衡的二叉查找树(BST) 性质 1.每个节点有红/黑标记位 2.根节点是黑色(硬性规定) 3.每个叶子节点(NIL)都是黑色的虚节点(由此引出性质5) 叶子节点 colo ...

  2. 数据结构学习笔记(七):哈希表(Hash Table)

    目录 1 哈希表的含义与结构特点 1.1 哈希(Hash)即无序 1.2 从数组看哈希表的结构特点 2 哈希函数(Hash Function)与哈希冲突(Hash Collision) 2.1 哈希函 ...

  3. 数据结构学习笔记(六):二叉树(Binary Tree)

    目录 1 背景知识:树(Tree) 2 何为二叉树(Binray Tree) 2.1 二叉树的概念与结构 2.2 满二叉树与完全二叉树 2.3 二叉树的三种遍历方式 3 二叉树及其遍历的简单实现(Ja ...

  4. 数据结构学习笔记(五):重识字符串(String)

    目录 1 字符串与数组的关系 1.1 字符串与数组的联系 1.2 字符串与数组的区别 2 实现字符串的链式存储(Java) 3 子串查找的简单实现 1 字符串与数组的关系 1.1 字符串与数组的联系 ...

  5. 数据结构学习笔记(四):重识数组(Array)

    目录 1 数组通过索引访问元素的原理 1.1 内存空间的连续性 1.2 数据类型的同一性 2 数组与链表增删查操作特性的对比 2.1 数组与链表的共性与差异 2.2 数组与链表增删查特性差异的原理 3 ...

  6. 数据结构学习笔记——顺序表的基本操作(超详细最终版+++)建议反复看看ヾ(≧▽≦*)o

    目录 前言 一.顺序表的定义 二.顺序表的初始化 三.顺序表的建立 四.顺序表的输出 五.顺序表的逆序输出 六.顺序表的插入操作 七.顺序表的删除操作 八.顺序表的按位和按值查找 基本操作的完整代码 ...

  7. Python数据结构学习笔记——链表:无序链表和有序链表

    目录 一.链表 二.无序链表 实现步骤分析 三.无序链表的Python实现代码 四.有序链表 实现步骤分析 五.有序链表的Python实现代码 结语 一.链表 链表中每一个元素都由为两部分构成:一是该 ...

  8. Python数据结构学习笔记——队列和双端队列

    目录 一.队列的定义 二.队列 实现步骤分析 三.队列的Python实现代码 四.队列的应用 六人传土豆游戏 五.双端队列的定义 六.双端队列 实现步骤分析 七.双端队列的Python实现代码 八.双 ...

  9. Python数据结构学习笔记——栈

    目录 一.栈的定义和特性 (一)栈的定义 (二)栈的反转特性 二.实现分析步骤 三.栈的Python实现代码 四.栈的应用 (一)匹配圆括号 (二)匹配符号 (三)模2除法(十进制转二进制) (四)进 ...

最新文章

  1. 【读书笔记】iOS-属性
  2. 第 30 章 lvs-rrd
  3. discuz 的 group.php,DiscuzX用户组过期问题解决方法
  4. lisp求面与面的差集_AcGeVector3d是点阵的集合,通过等分点的差集得到。 新的点可以通过点与点阵相差得......
  5. 使用TestBed测试具有依赖关系的Angular服务
  6. java excel解析视频教程_java解析Excel(xls、xlsx两种格式)
  7. 多终端数据同步机制设计
  8. 剑指Offer - 面试题40. 最小的k个数(排序/大顶堆)
  9. revit模型怎么在手机上看_怎么在手机上查看建筑模型图??
  10. 读书笔记《松本行弘的程序世界》
  11. Hive 安全模式下 Prepare Plan 的时间分析
  12. 股票经典书籍推荐(豪华版)
  13. 图像尺寸与图像像素、分辨率的关系
  14. 【路径规划】基于模糊逻辑系统实现车辆的自主导航附matlab代码
  15. 【解决笔记本电脑声音音量低的方法】
  16. 关于virtualbox虚拟电脑控制台严重错误解决方法。。。(博客园搬家至此,尚未整理)(六)
  17. Solr 配置文件之core.properties、solr.xml和Config Sets
  18. React Native解决在ios13暗黑模式下状态栏都为白色的问题
  19. oracle期末(一)
  20. 携程风险防御体系的变革之路

热门文章

  1. 工艺角(process corner)
  2. 【从0开始自学C/C++并在2023年10月之前找到一份工作之C++基础】
  3. 数值分析——LU分解求解线性方程组的Python实现
  4. 蒙特卡洛(Monte Carlo)法求定积分
  5. debussy vhdl co-simulation
  6. 微信小程序 页面路由 实现页面切换
  7. 超融合一体机概述及优势
  8. 《抗压力-逆境生存法则》读后感
  9. 3D成像方法 汇总(原理解析)--- 双目视觉、激光三角、结构光、ToF、光场、全息...
  10. 【转】活灵活现用Git--基础篇