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

定义

  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进行右旋
在这里插入图片描述在这里插入图片描述
  • 总结一下几个结点的规律
结点缩写左旋规律右旋规律
EEE.parent 更新为S
E.right更新为S.left
E.right 更新为 S
E.parent 更新为 S.parent
SSS.left 更新为 E
S.parent 更新为 E.parent
S.parent 更新为E
S.left更新为E.right
between E and SBESBES.parent 更新为 EBES.parent 更新为 S
less than ELE不受任何影响不受任何影响
greater than SGS不受任何影响不受任何影响
当前子树的父结点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可以转为LL RL可以转为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 : delaytime
    delayDo(arr, v => cb(v), delaytime);
}
// 插入元素间隔 200 毫秒
main([...Array(20).keys()].map(v=>v+1), process.insert, 800);

参考资料

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

www.cs.usfca.eduRed/Black Tree 数据结构可视化
ProgramizRed-Black Tree
AlgorithmtutorRed Black Trees (with implementation in C++, Java, and Python)

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6