本章节简单的介绍一下红黑树
前言
在上一篇章中,我们讲到了二叉搜索树,二叉搜索树有一个缺点,比如我们把元素【1,2,3,4,5】依次插入,就会变成下面的样子
就不再是树的结构,而是一个链表,那红黑树的出现就是为了解决左右不平衡
红黑树定义
红黑树是一种自平衡的二叉查找树。
- 二叉查找树:首先,它具备普通二叉查找树的所有特性,即任何节点的左子树上所有节点的值都小于它,右子树上所有节点的值都大于它。这使得查找、插入、删除等操作非常高效(平均时间复杂度为 O(log n))。
- 自平衡:这是关键。普通的二叉查找树在极端情况下(比如一直插入有序的数据)会退化成一条链表,操作效率会暴跌至 O(n)。红黑树通过一套严格的规则和调整操作,确保树始终保持“大致平衡”,从而保证了最坏情况下操作效率依然为 O(log n)。
你可以把它理解为一种“智能”的、不会变瘸腿的二叉查找树。
红黑树需要遵守哪些规则
红黑树通过以下五条规则来约束自己,维持平衡:
- 节点是红色或黑色:每个节点都有一个颜色属性,非红即黑。
- 根节点是黑色:树的最顶端的根节点必须是黑色的。
- 所有叶子节点都是黑色:这里的叶子节点指的是为空(NIL或NULL)的节点,它们被认为是黑色的。
- 红色节点的子节点必须是黑色(即不能有两个连续的红色节点):这条规则是平衡的关键,它确保了从根到叶子的任何路径上,不会出现两个连续的红色节点,从而限制了路径的最大长度。
- 从任一节点到其每个叶子节点的所有路径都包含相同数目的黑色节点:这条规则是平衡的另一关键,它确保了没有一条路径会比其他路径长出两倍以上,树是大致平衡的。
一个简单的比喻:
想象一下这些规则就像交通规则。规则4(红节点的子节点必须为黑)就像“红灯后必须是绿灯”,防止连续拥堵(过长的路径)。规则5(黑高相同)就像确保所有车道(路径)的“基础长度”是一样的。通过这些规则,整个交通系统(树结构)就能高效有序地运行。
如何插入和删除
当我们插入或删除一个节点时,可能会破坏上面的规则(尤其是规则4和5)。这时,红黑树不会坐视不管,它会通过两种核心操作来修复自己:
-
变色:改变节点的颜色(红变黑或黑变红)。这是最直接和首选的调整方式。
-
旋转:通过左旋或右旋来改变树的结构,就像把树枝掰正。
- 左旋:以某个节点为支点,让其右子节点成为新的父节点,自己成为其左子节点。
- 右旋:以某个节点为支点,让其左子节点成为新的父节点,自己成为其右子节点。
插入新节点时:
- 新插入的节点默认总是红色(为什么?因为插入红色节点破坏规则4的可能性比破坏规则5的可能性要小,调整起来更简单)。
- 如果插入后破坏了规则(比如出现了两个连续的红色节点),就会通过一系列的变色和旋转操作,直到所有规则重新被满足为止。
优点和用途
优点(对比其他平衡树,如AVL树):
-
查询、插入、删除的综合性能都很好,时间复
杂度都是 O(log n) 。
-
与严格的平衡二叉树(AVL树)相比,红黑树对平衡的要求稍微宽松一些(只是“大致平衡”)。这意味着它在插入和删除操作时需要的旋转操作更少,所以在频繁增删的场景下性能更好。
主要用途:
由于其高效的性能,它被广泛应用于各种系统和语言的底层实现中,例如:
- Java:
TreeMap,TreeSet,HashMap(在解决哈希冲突的链表长度大于8时,会转为红黑树)。 - C++ :STL中的
map,multimap,set,multiset。 - Linux内核:进程调度、内存管理等模块都需要高效的数据结构。
- 数据库:数据库索引的实现也常常用到红黑树或其变体。
代码实现
首先定义颜色枚举
// 定义节点颜色枚举
enum Color {
RED = "RED",
BLACK = "BLACK"
}
然后定义红黑树节点类
// 定义红黑树节点类
class RBNode<T> {
data: T;
color: Color;
left: RBNode<T> | null;
right: RBNode<T> | null;
parent: RBNode<T> | null;
constructor(data: T) {
this.data = data;
this.color = Color.RED; // 新节点默认为红色
this.left = null;
this.right = null;
this.parent = null;
}
// 判断节点是否为红色
isRed(): boolean {
return this.color === Color.RED;
}
// 判断节点是否为黑色
isBlack(): boolean {
return this.color === Color.BLACK;
}
// 将节点设为红色
setRed(): void {
this.color = Color.RED;
}
// 将节点设为黑色
setBlack(): void {
this.color = Color.BLACK;
}
}
这里面相对于二叉搜索树,多了一个parent属性,这个后面会讲到
初步定义红黑树类
// 定义红黑树类
class RedBlackTree<T> {
private root: RBNode<T> | null;
private size: number;
constructor() {
this.root = null;
this.size = 0;
}
// 获取树的大小
getSize(): number {
return this.size;
}
// 判断树是否为空
isEmpty(): boolean {
return this.size === 0;
}
// 查找节点
find(data: T): RBNode<T> | null {
return this.findNode(this.root, data);
}
private findNode(node: RBNode<T> | null, data: T): RBNode<T> | null {
if (node === null) return null;
if (data < node.data) {
return this.findNode(node.left, data);
} else if (data > node.data) {
return this.findNode(node.right, data);
} else {
return node;
}
}
}
接下来我们只实现红黑树里面最复杂的两个功能【插入,删除】
在讲插入删除节点操作之前,先讲解一下旋转操作,旋转分为左旋和右旋
旋转
左旋:想象把节点的右孩子"提上来"作为新的父节点
// 左旋
private leftRotate(node: RBNode<T>): void {
const rightChild = node.right!;
node.right = rightChild.left;
if (rightChild.left !== this.NIL) {
rightChild.left.parent = node;
}
rightChild.parent = node.parent;
if (node.parent === null || node.parent === this.NIL) {
this.root = rightChild;
} else if (node === node.parent.left) {
node.parent.left = rightChild;
} else {
node.parent.right = rightChild;
}
rightChild.left = node;
node.parent = rightChild;
}
右旋:与左旋对称,把节点的左孩子"提上来"
// 右旋
private rightRotate(node: RBNode<T>): void {
const leftChild = node.left!;
node.left = leftChild.right;
if (leftChild.right !== this.NIL) {
leftChild.right.parent = node;
}
leftChild.parent = node.parent;
if (node.parent === null || node.parent === this.NIL) {
this.root = leftChild;
} else if (node === node.parent.right) {
node.parent.right = leftChild;
} else {
node.parent.left = leftChild;
}
leftChild.right = node;
node.parent = leftChild;
}
插入节点
在这里再次重复一下红黑树的五条基本原则
想象这些是维持家族和谐的"家规":
- 每个节点非红即黑 - 每个人要么穿红衣服,要么穿黑衣服
- 根节点总是黑色 - 族长必须穿黑衣服
- 所有叶子节点(null)是黑色 - 家族边界守卫都穿黑衣服
- 红色节点的孩子必须是黑色 - 不能有两个连续红衣服的人站在一起
- 从任何节点到其所有叶子节点的路径包含相同数量的黑色节点 - 每条家族支系的"黑节点深度"相同
插入的核心思想
默认策略:新插入的节点总是红色的(为什么?因为插入红色节点破坏规则4的可能性比破坏规则5的可能性小,调整起来更简单)。
插入后检查:如果破坏了规则(主要是规则4:出现连续红色节点),就需要通过变色和旋转来修复。
插入后情况分析
情况 1:叔叔节点是红色
-
场景:新节点(红)的父节点是红色,且叔叔节点(父节点的兄弟)也是红色。
-
修复方法:
- 将父节点和叔叔节点改为黑色。
- 将祖父节点改为红色(因为祖父原本一定是黑色,否则违反父子不能同为红色的性质)。
- 以祖父节点为新的 “当前节点”,继续向上检查(因为祖父变色后可能与它的父节点冲突)。
情况 2:叔叔节点是黑色(或不存在),且新节点是父节点的右孩子,父节点是祖父的左孩子
-
场景:新节点(红)的父节点是红色,叔叔节点是黑色(或 null,视为黑色),且新节点、父节点、祖父节点构成 “右斜” 结构(父节点是祖父的左孩子,新节点是父的右孩子)。
-
修复方法:
- 对父节点进行左旋转,将结构转为 “左斜”(新节点成为父节点,原父节点成为新节点的左孩子)。
- 转换后变为情况 3,继续处理。
情况 3:叔叔节点是黑色(或不存在),且新节点是父节点的左孩子,父节点是祖父的左孩子
-
场景:新节点(红)的父节点是红色,叔叔节点是黑色(或 null),且新节点、父节点、祖父节点构成 “左斜” 结构(父节点是祖父的左孩子,新节点是父的左孩子)。
-
修复方法:
- 对祖父节点进行右旋转,使父节点成为新的祖父。
- 交换父节点和原祖父节点的颜色(父节点变黑,原祖父变红)。
- 此时树已平衡,无需继续向上检查。
补充说明
- 上述情况是针对 “父节点是祖父节点的左孩子” 的场景,对于 “父节点是祖父节点的右孩子” 的对称情况,处理逻辑相同,只需将旋转方向反转(左旋转变右旋转,右旋转变左旋转)。
- 修复操作的核心是通过变色减少红色节点的冲突,通过旋转调整树的结构,最终确保红黑树的 5 条性质(根黑、叶黑、红子必黑、路径黑数相同、新节点为红)全部满足。
这些情况覆盖了插入后可能出现的所有失衡场景,通过递归或循环处理,总能在有限步骤内恢复红黑树的平衡。
插入步骤实现
接下来我们依次插入以下元素【1,2,3,4,5,6,7,8,9,10】
插入 1
新插入的节点是红色的,这里为什么是黑色的呢,因为要符合根节点总是黑色,这里进行了变色操作
插入 2
将红色节点2插入到黑色节点1右子树
经过检查,符合五条基本原则,无需变色,无需旋转
插入 3
将3插入到2左子树上
新节点默认是红色
-
按照BST规则,3 > 2,所以插入为2的右孩子
-
检查是否违反规则:
- 父节点2是红色,出现了连续红色节点(违反规则4)
- 需要修复
我们需要检查节点3的叔叔节点(父节点2的兄弟节点):
- 节点2的父节点是1
- 节点1的左孩子是空(NIL节点,视为黑色)
- 所以节点3的叔叔节点是NIL(黑色)
情况判断:
- 父节点2是红色
- 叔叔节点是黑色(NIL)
- 当前节点3是父节点2的右孩子
- 父节点2是祖父节点1的右孩子
这属于情况2:叔叔节点是黑色,且当前节点是父节点的右孩子
修复步骤:
- 对祖父节点1进行左旋
- 将祖父节点1变为红色
- 将父节点2变为黑色
步骤1执行完毕后
步骤2,3执行完毕后
插入 4
将4插入到3的右子树下面
编辑
现在我们需要检查是否违反了红黑树的规则:
- 节点3是红色,节点4也是红色 → 违反了规则4(不能有连续红色节点)
- 需要修复
分析修复情况
从节点4开始向上分析:
- 节点4的父节点是3(红色)
- 节点4的祖父节点是2(黑色)
- 节点4的叔叔节点(父节点3的兄弟)是1(红色)
这属于情况1:叔叔节点是红色
执行修复步骤
情况1的处理方法:
- 将父节点(3)和叔叔节点(1)变为黑色
- 将祖父节点(2)变为红色
- 将祖父节点(2)作为当前节点,继续向上检查
执行步骤:
- 节点3变为黑色
- 节点1变为黑色
- 节点2变为红色
插入 5
将5插入到4的右子树下面
现在我们需要检查是否违反了红黑树的规则:
- 节点4是红色,节点5也是红色 → 违反了规则4(不能有连续红色节点)
- 需要修复
分析修复情况
- 节点5的父节点是4(红色)
- 节点5的祖父节点是3(黑色)
- 节点5的叔叔节点是NIL(黑色)
当前这种情况和插入 3 的情况类似,我们可以按照处理3的步骤处理5
处理方法:
-
节点4变为黑色
-
节点3变为红色
-
以节点3为支点进行左旋
- 节点4成为节点3的父节点
- 节点3的右指针指向节点4的左子树(NIL)
- 节点4的左指针指向节点3
执行步骤:
-
以节点4为支点进行左旋
- 节点5成为节点4的父节点
- 节点4成为节点5的左孩子
左旋后的数据结构
在经过变色
插入 6
将 6 插入到 5的右子树下面
现在我们需要检查是否违反了红黑树的规则:
- 节点6是红色,节点5也是红色 → 违反了规则4(不能有连续红色节点)
- 需要修复
分析修复情况
- 节点6的父节点是5(红色)
- 节点6的祖父节点是4(黑色)
- 节点6的叔叔节点是3(红色)
这属于情况1,我们按照情况1的方式去处理
- 将父节点和叔叔节点改为黑色。
- 将祖父节点改为红色(因为祖父原本一定是黑色,否则违反父子不能同为红色的性质)。
- 以祖父节点为新的 “当前节点”,继续向上检查(因为祖父变色后可能与它的父节点冲突)。
关键点
- 情况1(叔叔节点是红色) 的处理方法:变色(父节点和叔叔节点变黑,祖父节点变红)
- 修复后可能需要继续向上检查:祖父节点变为红色后,可能与其父节点形成新的连续红色节点问题
- 在这种情况下不需要旋转:当叔叔节点是红色时,只需要变色操作,不需要旋转
插入 7
将7插入到6的右子树下面
现在我们需要检查是否违反了红黑树的规则:
- 节点6是红色,节点7也是红色 → 违反了规则4(不能有连续红色节点)
- 需要修复
分析修复情况
- 节点7的父节点是6(红色)
- 节点7的祖父节点是5(黑色)这是RR情况(父节点是祖父节点的右孩子,当前节点是父节点的右孩子)。
- 节点7的叔叔节点是NIL(黑色)
执行步骤:
- 以节点5为支点进行左旋
- 节点5变为红色
- 节点6变为黑色
讲过左旋后
讲过变色后
插入 8
插入后
讲过检查,7和8为连续的红色,需要修复
分析修复情况
- 节点8的父节点是7(红色)
- 节点8的祖父节点是6(黑色)
- 节点8的叔叔节点是5(红色)
这属于情况 1 ,情况 1 的处理方案:
- 将父节点和叔叔节点改为黑色。
- 将祖父节点改为红色(因为祖父原本一定是黑色,否则违反父子不能同为红色的性质)。
- 以祖父节点为新的 “当前节点”,继续向上检查(因为祖父变色后可能与它的父节点冲突)。
继续向上检查,发现 4和6为连续的红色,需要继续修复
现在我们需要检查节点6(当前节点):
- 节点6是红色
- 节点6的父节点是4(红色)→ 出现了连续红色节点(违反规则4)
- 需要继续修复
现在分析节点6的修复情况:
- 节点6的父节点是4(红色)
- 节点6的祖父节点是2(黑色)
- 节点6的叔叔节点(父节点4的兄弟)是3(黑色)
这属于情况2:叔叔节点是黑色,且当前节点是父节点的右孩子
修复步骤:
- 对祖父节点2进行左旋
- 将祖父节点2变为红色
- 将父节点4变为黑色
祖父节点 2 经过左旋后
经过左旋后,节点2将右子树节点4提上来,然后节点4原先的左子树变成节点2的右子树
接下来变色处理
插入 9
9 插入到 8的右子树下面
经过检查,8和9为连续的红色,需要修复
分析修复情况
- 节点9的父节点是8(红色)
- 节点9的祖父节点是7(黑色)
- 节点9的叔叔节点是NIL(黑色)
这属于情况 2
处理步骤
- 对节点7进行左旋
- 对节点8变为黑色
- 对节点7变为红色
插入 10
经过检查,9和10为连续的红色,需要修复
分析修复情况
- 节点10的父节点是9(红色)
- 节点10的祖父节点是8(黑色)
- 节点10的叔叔节点是7(红色)
这属于情况1
修复步骤:
- 将节点8改为红色
- 将节点7改为黑色
- 将节点9改为黑色
再次向上检查,发现节点6和节点8为连续的红色,需要再次修复
分析修复情况
- 节点8的父节点是6(红色)
- 节点8的祖父节点是4(黑色)
- 节点8的叔叔节点是2(红色)
这属于情况1
修复步骤:
- 将节点4改为红色
- 将节点6改为黑色
- 将节点2改为黑色
再次检查,发现节点4不符合根节点总是黑色的原则, 需要进行修复
修复步骤
将节点4改为黑色
插入逻辑代码
// 插入节点
insert(data: T): void {
const newNode = new RBNode(data);
newNode.left = this.NIL;
newNode.right = this.NIL;
this.insertNode(this.root, newNode);
this.insertFixup(newNode);
this.size++;
}
private insertNode(root: RBNode<T>, node: RBNode<T>): void {
let parent: RBNode<T> = this.NIL;
let current: RBNode<T> = root;
while (current !== this.NIL) {
parent = current;
if (node.data < current.data) {
current = current.left!;
} else {
current = current.right!;
}
}
node.parent = parent;
if (parent === this.NIL) {
this.root = node;
} else if (node.data < parent.data) {
parent.left = node;
} else {
parent.right = node;
}
}
// 插入后修复红黑树性质
private insertFixup(node: RBNode<T>): void {
let current = node;
while (current.parent !== null && current.parent.isRed()) {
if (current.parent === current.parent.parent!.left) {
const uncle = current.parent.parent!.right;
if (uncle !== null && uncle.isRed()) {
// 情况1:叔节点是红色
current.parent.setBlack();
uncle.setBlack();
current.parent.parent!.setRed();
current = current.parent.parent!;
} else {
if (current === current.parent.right) {
// 情况2:当前节点是父节点的右子节点
current = current.parent;
this.leftRotate(current);
}
// 情况3:当前节点是父节点的左子节点
current.parent!.setBlack();
current.parent!.parent!.setRed();
this.rightRotate(current.parent!.parent!);
}
} else {
// 对称的情况
const uncle = current.parent.parent!.left;
if (uncle !== null && uncle.isRed()) {
current.parent.setBlack();
uncle.setBlack();
current.parent.parent!.setRed();
current = current.parent.parent!;
} else {
if (current === current.parent.left) {
current = current.parent;
this.rightRotate(current);
}
current.parent!.setBlack();
current.parent!.parent!.setRed();
this.leftRotate(current.parent!.parent!);
}
}
}
this.root!.setBlack();
}
删除节点
删除的核心思想
红黑树删除节点后的修复操作比插入更复杂,其核心是处理 “黑色节点被删除后导致的路径黑色节点数失衡” 问题。修复操作的场景主要基于被删除节点的替代节点(即实际从树中移除的节点,记为x)的兄弟节点(记为s)的颜色,以及兄弟节点的子节点(侄子节点)的颜色来划分,共分为4 种基本情况(每种情况都有对称场景,即x是左孩子还是右孩子的镜像情况)。
前提说明
- 红黑树删除时,实际被移除的节点(
x)最多有一个子节点(因为若有两个子节点,会用后继节点替代,后继节点最多有一个子节点)。 - 若
x是红色,删除后不破坏红黑树性质,无需修复;仅当x是黑色时,才会导致路径黑色节点数减少,需要修复。 - 修复的目标是:恢复 “所有路径黑色节点数相同” 和 “无连续红色节点” 的性质。
4 种基本情况(以x是左孩子为例)
情况 1:x的兄弟节点s是红色
-
场景:
x是左孩子,父节点p是黑色(因为s是红色,父子不能同红),s是红色,s的子节点必为黑色(红子必黑)。 -
问题:
s是红色,无法直接通过s的子节点 “补充” 黑色,需先转化为s为黑色的情况。 -
修复方法:
- 对
p进行左旋转(使s成为p的父节点)。 - 交换
p和s的颜色(p变红,s变黑)。 - 旋转后,
s的原左孩子成为新的s(黑色),转化为情况 2、3、4。
- 对
情况 2:x的兄弟节点s是黑色,且s的两个孩子都是黑色
-
场景:
s是黑色,s的左孩子(sl)和右孩子(sr)均为黑色(或 NIL 节点,视为黑色)。 -
问题:
s及其子节点都无法提供额外黑色,需向上传递 “黑色缺失”。 -
修复方法:
- 将
s改为红色(此时s所在路径黑色数减少 1,与x路径平衡)。 - 令
x = p(将父节点作为新的x),继续向上修复(因为p所在路径可能因s变红而黑色数不足)。
- 将
情况 3:x的兄弟节点s是黑色,s的左孩子sl是红色,右孩子sr是黑色
-
场景:
s是黑色,sl是红色,sr是黑色(x是左孩子)。 -
问题:
s的右孩子无法直接补充黑色,需先转化为s右孩子为红色的情况。 -
修复方法:
- 对
s进行右旋转(使sl成为s的父节点)。 - 交换
s和sl的颜色(s变红,sl变黑)。 - 旋转后,
s成为新的sr,sl成为新的s,且s的右孩子(原s)为红色,转化为情况 4。
- 对
情况 4:x的兄弟节点s是黑色,且s的右孩子sr是红色
-
场景:
s是黑色,sr是红色(x是左孩子)。 -
问题:
sr可提供额外黑色,直接修复失衡。 -
修复方法:
- 对
p进行左旋转(使s成为新的父节点,p成为s的左孩子)。 - 交换
p和s的颜色(s继承p的颜色,p变黑)。 - 将
sr改为黑色(补充黑色,平衡路径)。 - 修复完成,
x设为根节点(退出循环)。
- 对
对称情况
上述 4 种情况均基于 “x是左孩子”,当 “x是右孩子” 时,场景完全对称:
- 兄弟节点
s的左右孩子判断反转(左→右,右→左)。 - 旋转方向反转(左旋转→右旋转,右旋转→左旋转)。
总结
红黑树删除后的修复操作核心是通过旋转调整结构和变色平衡黑色节点数,处理 “黑色缺失” 问题。4 种基本情况(含对称场景)覆盖了所有可能的失衡场景,通过情况间的转化,最终总能在有限步骤内恢复红黑树的性质。
我们还是以这个红黑树为例,演示一下删除节点的操作
删除步骤
删除 1
经过删除后
情况分析
- 节点1的兄弟节点3是黑色
- 节点1的父亲节点2是黑色
- 节点3的两个孩子都是黑色
这属于情况2,修复方法如下
- 将3改为红色(此时3所在路径黑色数减少 1,与1路径平衡)。
- 令
x = p(将父节点作为新的x),继续向上修复(因为2所在路径可能因3变红而黑色数不足)。
继续向上修复
情况分析
- 节点2的兄弟节点6是黑色
- 节点2的父亲节点4是黑色
- 节点6的左孩子是黑色,右孩子是红色
这属于情况4,x的兄弟节点s是黑色,且s的右孩子sr是红色
修复方法:
- 对
p进行左旋转(使s成为新的父节点,p成为s的左孩子)。 - 交换
p和s的颜色(s继承p的颜色,p变黑)。 - 将
sr改为黑色(补充黑色,平衡路径)。 - 修复完成,
x设为根节点(退出循环)。
将节点4进行左旋:
将节点8进行变色
删除 2
节点 2 只有一个子节点3
此时需要将父节点直接连接到子节点3
删除节点2
经过检查,符合五条基本原则
删除 4
节点 4 有两个子节点,当被删除的节点有两个子节点时,会用后继节点替代,后继节点最多有一个子节点
这一步我们可以按照正常的二叉搜索树来执行
1. 先定位自己的左子树
2. 在左子树里面,一直往右子树寻找,直到找到叶子节点
所以我们找节点4的后继子节点,(提示,这个后继子节点比4的左子树要大,比右子树要小)
我们将节点4的数值替换成3,然后原先的3执行删除操作
此时我们执行删除3的操作
3的父节点是4 (黑色)
3的兄弟节点是5(黑色)
5的两个孩子都是黑色
符合情况2: x的兄弟节点s是黑色,且s的两个孩子都是黑色
修复方法:
- 将
s改为红色(此时s所在路径黑色数减少 1,与x路径平衡)。 - 令
x = p(将父节点作为新的x),继续向上修复(因为p所在路径可能因s变红而黑色数不足)。
然后断开原来的节点4与节点3的连接
删除后继节点3
将兄弟节点5改为红色
此时我们再次分析现在的情况
3的兄弟节点8 (黑色)
节点8的两个孩子(黑色)
符合情况2:x的兄弟节点s是黑色,且s的两个孩子都是黑色
修复方法:
- 将8改为红色(此时8所在路径黑色数减少 1,与
x路径平衡)。 - 令
x = p(将父节点作为新的x),继续向上修复(因为p所在路径可能因s变红而黑色数不足)。
经过检查后,符合五项基本原则
删除 6
我们发现6有两个孩子
依照惯例,我们寻找6的后继子节点
逻辑步骤:
- 定位左子树
- 根据左子树节点一直遍历右子树节点
这个步骤和正常的二叉搜索树一样
将节点5的值放入在节点6里面
然后执行删除红色节点5的操作
我们发现红色节点5为红色,没有兄弟节点
我们可以直接执行删除操作
断开3和红色节点5的连接
删除 8
我们先找到节点8
发现节点8有两个孩子
然后我们按照正常的删除逻辑,先找左子树,再遍历右子树,找到节点7
数值替换
接下来删除黑色节点 7
由于黑色节点7没有孩子,可以直接断开黑色节点7和红色节点7的连接关系
然后我们发现
节点7的兄弟节点9是黑色,
节点9的右孩子10 是红色
符合情况4:x的兄弟节点s是黑色,且s的右孩子sr是红色
修复方法
- 对
p进行左旋转(使s成为新的父节点,p成为s的左孩子)。 - 交换
p和s的颜色(s继承p的颜色,p变黑)。 - 将
sr改为黑色(补充黑色,平衡路径)。 - 修复完成,
x设为根节点(退出循环)。
对红色节点 7 进行左旋
将9改为红色
将7改为黑色
将10改为黑色
删除节点就先讲到这里,后面的删除稍微简单一些
删除逻辑代码
// 删除节点
delete(data: T): boolean {
const node = this.find(data);
if (node === null || node === this.NIL) {
return false;
}
this.deleteNode(node);
this.size--;
return true;
}
private deleteNode(node: RBNode<T>): void {
let y = node;
let originalColor = y.color;
let x: RBNode<T>;
if (node.left === this.NIL) {
x = node.right!;
this.transplant(node, node.right!);
} else if (node.right === this.NIL) {
x = node.left!;
this.transplant(node, node.left!);
} else {
y = this.minimum(node.right!);
originalColor = y.color;
x = y.right!;
if (y.parent === node) {
x.parent = y;
} else {
this.transplant(y, y.right!);
y.right = node.right;
y.right!.parent = y;
}
this.transplant(node, y);
y.left = node.left;
y.left!.parent = y;
y.color = node.color;
}
if (originalColor === Color.BLACK) {
this.deleteFixup(x);
}
}
// 删除后修复红黑树性质
private deleteFixup(node: RBNode<T>): void {
let x = node;
while (x !== this.root && x.isBlack()) {
if (x === x.parent!.left) {
let w = x.parent!.right!;
if (w.isRed()) {
// 情况1:兄弟节点是红色
w.setBlack();
x.parent!.setRed();
this.leftRotate(x.parent!);
w = x.parent!.right!;
}
if (w.left!.isBlack() && w.right!.isBlack()) {
// 情况2:兄弟节点的两个子节点都是黑色
w.setRed();
x = x.parent!;
} else {
if (w.right!.isBlack()) {
// 情况3:兄弟节点的右子节点是黑色,左子节点是红色
w.left!.setBlack();
w.setRed();
this.rightRotate(w);
w = x.parent!.right!;
}
// 情况4:兄弟节点的右子节点是红色
w.color = x.parent!.color;
x.parent!.setBlack();
w.right!.setBlack();
this.leftRotate(x.parent!);
x = this.root!;
}
} else {
// 对称的情况
let w = x.parent!.left!;
if (w.isRed()) {
w.setBlack();
x.parent!.setRed();
this.rightRotate(x.parent!);
w = x.parent!.left!;
}
if (w.right!.isBlack() && w.left!.isBlack()) {
w.setRed();
x = x.parent!;
} else {
if (w.left!.isBlack()) {
w.right!.setBlack();
w.setRed();
this.leftRotate(w);
w = x.parent!.left!;
}
w.color = x.parent!.color;
x.parent!.setBlack();
w.left!.setBlack();
this.rightRotate(x.parent!);
x = this.root!;
}
}
}
x.setBlack();
}
完整代码
// 定义节点颜色枚举
enum Color {
RED = "RED",
BLACK = "BLACK"
}
// 定义红黑树节点类
class RBNode<T> {
data: T;
color: Color;
left: RBNode<T> | null;
right: RBNode<T> | null;
parent: RBNode<T> | null;
constructor(data: T) {
this.data = data;
this.color = Color.RED; // 新节点默认为红色
this.left = null;
this.right = null;
this.parent = null;
}
// 判断节点是否为红色
isRed(): boolean {
return this.color === Color.RED;
}
// 判断节点是否为黑色
isBlack(): boolean {
return this.color === Color.BLACK;
}
// 将节点设为红色
setRed(): void {
this.color = Color.RED;
}
// 将节点设为黑色
setBlack(): void {
this.color = Color.BLACK;
}
}
// 定义红黑树类
class RedBlackTree<T> {
private root: RBNode<T> | null;
private size: number;
private readonly NIL: RBNode<T>; // 哨兵节点,代表空节点
constructor() {
this.NIL = new RBNode(null as any);
this.NIL.setBlack();
this.root = this.NIL;
this.size = 0;
}
// 获取树的大小
getSize(): number {
return this.size;
}
// 判断树是否为空
isEmpty(): boolean {
return this.size === 0;
}
// 查找节点
find(data: T): RBNode<T> | null {
return this.findNode(this.root, data);
}
private findNode(node: RBNode<T> | null, data: T): RBNode<T> | null {
if (node === null || node === this.NIL) return null;
if (data < node.data) {
return this.findNode(node.left, data);
} else if (data > node.data) {
return this.findNode(node.right, data);
} else {
return node;
}
}
// 插入节点
insert(data: T): void {
const newNode = new RBNode(data);
newNode.left = this.NIL;
newNode.right = this.NIL;
this.insertNode(this.root, newNode);
this.insertFixup(newNode);
this.size++;
}
private insertNode(root: RBNode<T>, node: RBNode<T>): void {
let parent: RBNode<T> = this.NIL;
let current: RBNode<T> = root;
while (current !== this.NIL) {
parent = current;
if (node.data < current.data) {
current = current.left!;
} else {
current = current.right!;
}
}
node.parent = parent;
if (parent === this.NIL) {
this.root = node;
} else if (node.data < parent.data) {
parent.left = node;
} else {
parent.right = node;
}
}
// 插入后修复红黑树性质
private insertFixup(node: RBNode<T>): void {
let current = node;
while (current.parent !== null && current.parent.isRed()) {
if (current.parent === current.parent.parent!.left) {
const uncle = current.parent.parent!.right;
if (uncle !== null && uncle.isRed()) {
// 情况1:叔节点是红色
current.parent.setBlack();
uncle.setBlack();
current.parent.parent!.setRed();
current = current.parent.parent!;
} else {
if (current === current.parent.right) {
// 情况2:当前节点是父节点的右子节点
current = current.parent;
this.leftRotate(current);
}
// 情况3:当前节点是父节点的左子节点
current.parent!.setBlack();
current.parent!.parent!.setRed();
this.rightRotate(current.parent!.parent!);
}
} else {
// 对称的情况
const uncle = current.parent.parent!.left;
if (uncle !== null && uncle.isRed()) {
current.parent.setBlack();
uncle.setBlack();
current.parent.parent!.setRed();
current = current.parent.parent!;
} else {
if (current === current.parent.left) {
current = current.parent;
this.rightRotate(current);
}
current.parent!.setBlack();
current.parent!.parent!.setRed();
this.leftRotate(current.parent!.parent!);
}
}
}
this.root!.setBlack();
}
// 删除节点
delete(data: T): boolean {
const node = this.find(data);
if (node === null || node === this.NIL) {
return false;
}
this.deleteNode(node);
this.size--;
return true;
}
private deleteNode(node: RBNode<T>): void {
let y = node;
let originalColor = y.color;
let x: RBNode<T>;
if (node.left === this.NIL) {
x = node.right!;
this.transplant(node, node.right!);
} else if (node.right === this.NIL) {
x = node.left!;
this.transplant(node, node.left!);
} else {
y = this.minimum(node.right!);
originalColor = y.color;
x = y.right!;
if (y.parent === node) {
x.parent = y;
} else {
this.transplant(y, y.right!);
y.right = node.right;
y.right!.parent = y;
}
this.transplant(node, y);
y.left = node.left;
y.left!.parent = y;
y.color = node.color;
}
if (originalColor === Color.BLACK) {
this.deleteFixup(x);
}
}
// 删除后修复红黑树性质
private deleteFixup(node: RBNode<T>): void {
let x = node;
while (x !== this.root && x.isBlack()) {
if (x === x.parent!.left) {
let w = x.parent!.right!;
if (w.isRed()) {
// 情况1:兄弟节点是红色
w.setBlack();
x.parent!.setRed();
this.leftRotate(x.parent!);
w = x.parent!.right!;
}
if (w.left!.isBlack() && w.right!.isBlack()) {
// 情况2:兄弟节点的两个子节点都是黑色
w.setRed();
x = x.parent!;
} else {
if (w.right!.isBlack()) {
// 情况3:兄弟节点的右子节点是黑色,左子节点是红色
w.left!.setBlack();
w.setRed();
this.rightRotate(w);
w = x.parent!.right!;
}
// 情况4:兄弟节点的右子节点是红色
w.color = x.parent!.color;
x.parent!.setBlack();
w.right!.setBlack();
this.leftRotate(x.parent!);
x = this.root!;
}
} else {
// 对称的情况
let w = x.parent!.left!;
if (w.isRed()) {
w.setBlack();
x.parent!.setRed();
this.rightRotate(x.parent!);
w = x.parent!.left!;
}
if (w.right!.isBlack() && w.left!.isBlack()) {
w.setRed();
x = x.parent!;
} else {
if (w.left!.isBlack()) {
w.right!.setBlack();
w.setRed();
this.leftRotate(w);
w = x.parent!.left!;
}
w.color = x.parent!.color;
x.parent!.setBlack();
w.left!.setBlack();
this.rightRotate(x.parent!);
x = this.root!;
}
}
}
x.setBlack();
}
// 移植节点(用v替换u)
private transplant(u: RBNode<T>, v: RBNode<T>): void {
if (u.parent === null || u.parent === this.NIL) {
this.root = v;
} else if (u === u.parent.left) {
u.parent.left = v;
} else {
u.parent.right = v;
}
v.parent = u.parent;
}
// 找到最小节点
private minimum(node: RBNode<T>): RBNode<T> {
while (node.left !== this.NIL) {
node = node.left!;
}
return node;
}
// 左旋
private leftRotate(node: RBNode<T>): void {
const rightChild = node.right!;
node.right = rightChild.left;
if (rightChild.left !== this.NIL) {
rightChild.left.parent = node;
}
rightChild.parent = node.parent;
if (node.parent === null || node.parent === this.NIL) {
this.root = rightChild;
} else if (node === node.parent.left) {
node.parent.left = rightChild;
} else {
node.parent.right = rightChild;
}
rightChild.left = node;
node.parent = rightChild;
}
// 右旋
private rightRotate(node: RBNode<T>): void {
const leftChild = node.left!;
node.left = leftChild.right;
if (leftChild.right !== this.NIL) {
leftChild.right.parent = node;
}
leftChild.parent = node.parent;
if (node.parent === null || node.parent === this.NIL) {
this.root = leftChild;
} else if (node === node.parent.right) {
node.parent.right = leftChild;
} else {
node.parent.left = leftChild;
}
leftChild.right = node;
node.parent = leftChild;
}
// 中序遍历
inOrder(): T[] {
const result: T[] = [];
this.inOrderTraversal(this.root, result);
return result;
}
private inOrderTraversal(node: RBNode<T> | null, result: T[]): void {
if (node !== null && node !== this.NIL) {
this.inOrderTraversal(node.left, result);
result.push(node.data);
this.inOrderTraversal(node.right, result);
}
}
// 获取树的高度
getHeight(): number {
return this.calculateHeight(this.root);
}
private calculateHeight(node: RBNode<T> | null): number {
if (node === null || node === this.NIL) return 0;
return 1 + Math.max(this.calculateHeight(node.left), this.calculateHeight(node.right));
}
}