红黑树(Red-Black Tree)是一种自平衡的二叉查找树(BST)。每个节点包含一个颜色(红色或黑色)。
红黑树的性质
红黑树必须满足以下五个性质:
- 节点是红色或黑色:每个节点要么是红色的,要么是黑色的。
- 根节点是黑色:树的根节点必须是黑色的。
- 叶节点(NIL节点)是黑色的:树中的空节点或叶节点(即不存在子节点的节点)被认为是黑色的。
- 红色节点不能相邻:红色节点的两个子节点必须是黑色的。即如果一个节点是红色的,那么它的两个子节点必须是黑色的。
- 从每个节点到其所有后代叶节点的路径中,黑色节点的数量必须相同:从根节点到每个叶节点的路径上,必须包含相同数量的黑色节点(即黑色高度相同)。
这些规则保证了树的平衡性,从而在最坏情况下保持 O(log n) 的时间复杂度。
红黑树的基本操作
红黑树的基本操作包括插入、删除和查找。特别是插入和删除需要经过一系列的旋转(左旋和右旋)以及重新着色操作,以维护红黑树的性质。
在红黑树中,插入节点的过程需要保持树的平衡,并且满足红黑树的五个性质。插入操作通常会涉及两个主要步骤:
- 插入节点:按照二叉查找树的插入规则插入新节点。
- 调整树的平衡:通过旋转和重新着色来恢复红黑树的性质,尤其是红色节点不能相邻(即不能有两个连续的红色节点)。
常见情况的插入节点示例
1. 插入新节点到空树
情况:当红黑树为空时,新插入的节点将成为根节点,并且其颜色为黑色。
例子:
插入10B
10B
2.插入节点的父节点为黑色
情况:由于插入的节点是红色的,当插入节点的父节点是黑色时,不会影响红黑树的平衡,所以: 直接插入无需做自平衡。
例子:
插入节点 20:
10B
/ \
5R 15B
\
20R
此时,红黑树的性质没有违反,因为新节点的父节点是黑色,插入后不需要进行旋转或重新着色。
2. 插入节点的父节点为红色
情况:插入的节点的父节点是红色
2.1 父节点和叔叔节点都为红色
例子:
假设有以下红黑树:
10B
/ \
5R 20R
/
2R
插入节点 2,根据红色节点不能相邻需要进行调整:
- 首先,父节点和叔叔节点都变为黑色。
- 祖父节点
10变为红色。 - 如果祖父节点的父节点为黑色则无需处理,如果是红色,那么递归进行调整。
结果:
10R
/ \
5B 20B
/
2R
3. 插入节点的父节点是红色,叔叔节点是黑色,并且插在父亲的左节点
情况:插入的节点的父节点是红色,叔叔节点是黑色,并且插在父亲的左节点(LL型失衡)
例子:
假设我们有以下红黑树:
10B
/ \
5B 20B
/
2R
插入节点 1,根据红色节点不能相邻需要进行调整:
- 首先,父节点变为黑色。
- 祖父节点
5变为红色。 - 进行右旋
结果:
10B
/ \
2B 20B
/ \
1R 5R
4. 插入节点的父节点是红色,叔叔节点是黑色,并且插在父亲的右节点
情况:插入节点的父节点是红色,叔叔节点是黑色,并且插在父亲的右节点(LR失衡)
例子:
假设我们有以下红黑树:
10B
/
5R
插入节点 6,根据红色节点不能相邻需要进行调整:
- 首先,节点
6进行左旋。得到LL型失衡,后续参考LL型失衡处理
// 左旋结果
10B
/
6R
/
5R
// 最终结果
6B
/ \
5R 10R
5. 插入节点的父节点是红色,叔叔节点是黑色,并且父亲在祖父节点的右节点
情况:插入节点的父节点是红色,叔叔节点是黑色,并且父亲在祖父节点的右节点,并且插在父亲节点的右节点(RR失衡)
例子:
假设我们有以下红黑树:
10B
\
20R
插入节点 25,根据红色节点不能相邻需要进行调整:
- 首先,父节点变为黑色。
- 祖父节点
10变为红色。 - 进行左旋
// 左旋结果
20B
/ \
10R 25R
情况:插入节点的父节点是红色,叔叔节点是黑色,并且父亲在祖父节点的右节点,并且插在父亲节点的左节点(RL失衡)
例子:
假设我们有以下红黑树:
10B
\
20R
插入节点 18,根据红色节点不能相邻需要进行调整:
- 首先,进行右旋变成RR型失衡
- 参考RR型失衡处理
// 右旋结果
20B
\
18R
\
20R
// 最终结果
18B
/ \
10R 20R
实现
接下来,我们通过JavaScript实现一个简单的红黑树。
class Node {
constructor(data) {
this.data = data; // 节点存储的数据
this.color = 'red'; // 节点的颜色,默认为红色
this.left = null; // 左子节点
this.right = null; // 右子节点
this.parent = null; // 父节点
}
}
class RedBlackTree {
constructor() {
this.TNULL = new Node(null); // 叶节点(NIL节点)
this.TNULL.color = 'black'; // NIL节点是黑色的
this.root = this.TNULL; // 根节点初始化为NIL
}
// 左旋操作
leftRotate(x) {
let y = x.right;
x.right = y.left;
if (y.left !== this.TNULL) {
y.left.parent = x;
}
y.parent = x.parent;
if (x.parent === null) {
this.root = y;
} else if (x === x.parent.left) {
x.parent.left = y;
} else {
x.parent.right = y;
}
y.left = x;
x.parent = y;
}
// 右旋操作
rightRotate(x) {
let y = x.left;
x.left = y.right;
if (y.right !== this.TNULL) {
y.right.parent = x;
}
y.parent = x.parent;
if (x.parent === null) {
this.root = y;
} else if (x === x.parent.right) {
x.parent.right = y;
} else {
x.parent.left = y;
}
y.right = x;
x.parent = y;
}
// 插入修复操作
fixInsert(k) {
while (k.parent.color === 'red') {
if (k.parent === k.parent.parent.left) {
let u = k.parent.parent.right; // 叔节点
if (u.color === 'red') {
// 情况1:叔节点是红色
u.color = 'black';
k.parent.color = 'black';
k.parent.parent.color = 'red';
k = k.parent.parent;
} else {
if (k === k.parent.right) {
// 情况2:当前节点是右子节点
k = k.parent;
this.leftRotate(k); //LR型
}
// 情况3:当前节点是左子节点
k.parent.color = 'black';
k.parent.parent.color = 'red';
this.rightRotate(k.parent.parent);
}
} else {
let u = k.parent.parent.left; // 叔节点
if (u.color === 'red') {
// 情况1:叔节点是红色
u.color = 'black';
k.parent.color = 'black';
k.parent.parent.color = 'red';
k = k.parent.parent;
} else {
if (k === k.parent.left) {
// 情况2:当前节点是左子节点
k = k.parent;
this.rightRotate(k);
}
// 情况3:当前节点是右子节点
k.parent.color = 'black';
k.parent.parent.color = 'red';
this.leftRotate(k.parent.parent);
}
}
if (k === this.root) break;
}
this.root.color = 'black'; // 根节点始终是黑色
}
// 插入节点
insert(data) {
let newNode = new Node(data);
newNode.left = this.TNULL;
newNode.right = this.TNULL;
let parent = null;
let find_node = this.root;
while (find_node !== this.TNULL) {
parent = find_node;
if (newNode.data < find_node.data) {
find_node = find_node.left;
} else {
find_node = find_node.right;
}
}
if (parent === null) {
this.root = newNode;
newNode.color = 'black';
return;
} else {
newNode.parent = parent;
if (newNode.data < parent.data) {
parent.left = newNode;
} else {
parent.right = newNode;
}
}
this.fixInsert(newNode); // 插入后修复红黑树的性质
}
// 中序遍历
inorderHelper(node) {
if (node !== this.TNULL) {
this.inorderHelper(node.left);
console.log(node.data);
this.inorderHelper(node.right);
}
}
inorder() {
this.inorderHelper(this.root);
}
}
// 测试代码
const rbt = new RedBlackTree();
rbt.insert(10);
rbt.insert(20);
rbt.insert(30);
rbt.insert(15);
rbt.insert(25);
console.log('中序遍历结果:');
rbt.inorder(); // 输出:10, 15, 20, 25, 30