概念
规则
- 顾名思义,每个节点不是红的就是黑的;
- 树的根节点是黑的;
- 所有叶节点都是黑的(用NULL 引用表示的节点);
- 如果一个节点是红的,那么它的两个子节点都是黑的;
- 不能有两个相邻的红节点(一个红节点不能有红的父节点或子节点);
- 从给定的节点到它的后代节点(NULL 叶节点)的所有路径包含相同数量的黑色节点。
代码实现
红黑树仍然是个自平衡二叉搜索树,所以可以继承AVLTree
1 构造红黑结点与红黑树结构
class RedBlackNode extends Node {
constructor(key) {
super(key);
this.key = key;
this.color = Colors.RED;
this.parent = null;
}
// 判断结点是否为红色
isRed() {
return this.color === Colors.RED;
}
}
class RedBlackTree extends BinarySearchTree {
constructor() {
super();
this.root = null;
}
}
2 insert 插入
向红黑树插入节点和在二叉搜索树中是一样的。
除了插入的代码,我们还要在插入后给节点应用一种颜色,并且验证树是否满足红黑树的条件以及是否还是自平衡的。
insert(key) {
if (this.root === null) {
this.root = new RedBlackNode(key);
this.root.color = Colors.BLACK;
} else {
const newNode = this.insertNode(this.root);
this.fixTreeProperties(newNode);
}
}
insertNode(node, key) {
// 小于当前结点
if (this.compareFn(key, node.key) === Compare.LES_THAN) {
if (node.left === null) {
node.left = new RedBlackNode(key);
node.left.parent = node;
return node.left;
} else {
// 向下递归直到左子结点为null
return this.insertNode(node.left, key);
}
} else if (node.right === null) {
node.right = new RedBlackNode(key);
node.right.parent = node;
} else {
return this.insertNode(node.right, key);
}
}
3 重构红黑树
要验证红黑树是否还是平衡的以及满足它的所有要求,我们需要使用两个概念:重新填色和旋转。
- 在向树中插入节点后,新节点将会是红色。
- 这不会影响黑色节点数量的规则(规则6),但会影响规则5:两个后代红节点不能共存。
- 如果插入节点的父节点是黑色,那没有问题。但是如果插入节点的父节点是红色,那么会违反规则5。
- 要解决这个冲突,我们只需要改变父节点、祖父节点和叔节点(因为我们同样改变了父节点的颜色)
while (node &&
node.parent &&
node.parent.color.isRed() &&
node.color !== Colors.BLACK) {}
情况A:父节点是左侧子节点
- 叔节点也是红色——只需要重新填色
- 节点是右侧子节点——左旋转
- 节点是左侧子节点——右旋转
let parent = node.parent;
const grandParent = parent.parent;
// 情形A:父节点是左侧子节点
if (grandParent && grandParent.left === parent) {
const uncle = grandParent.right;
// 情形A1:叔节点也是红色——只需要重新填色
if (uncle && uncle.color === Colors.RED) {
grandParent.color = Colors.RED;
parent.color = Colors.BLACK;
uncle.color = Colors.BLACK;
node = grandParent;
} else {
// 情形A2:节点是右侧子节点——左旋转
if (node === parent.right) {
this.rotationRR(parent);
node = parent;
parent = node.parent;
}
// 情形A3:节点是左侧子节点——右旋转
this.rotationLL(grandParent);
parent.color = Colors.BLACK;
grandParent.color = Colors.RED;
node = parent;
}
}
情况B:父节点是右侧子节点
- 叔节点是红色——只需要重新填色
- 节点是左侧子节点——右旋转
- 节点是右侧子节点——左旋转
const uncle = grandParent.left;
// 情形B1:叔节点是红色——只需要重新填色
if (uncle && uncle.color === Colors.RED) {
grandParent.color = Colors.RED;
parent.color = Colors.BLACK;
uncle.color = Colors.BLACK;
node = grandParent;
} else {
// 情形B2:节点是左侧子节点——左旋转
if (node === parent.left) {
this.rotationLL(parent);
node = parent;
parent = node.parent;
}
// 情形B3:节点是右侧子节点——左旋转
this.rotationRR(grandParent);
parent.color = Colors.BLACK;
grandParent.color = Colors.RED;
node = parent;
}
4 旋转方式
右旋转
rotationLL(node) {
const tmp = node.left;
node.left = tmp.right;
if (tmp.right && tmp.right.key) {
tmp.right.parent = node;
}
tmp.parent = node.parent;
if (!node.parent) {
this.root = tmp;
}
else {
if (node === node.parent.left) {
node.parent.left = tmp;
}
else {
node.parent.right = tmp;
}
}
tmp.right = node;
node.parent = tmp;
}
左旋转
rotationRR(node) {
const tmp = node.right;
node.right = tmp.left;
if (tmp.left && tmp.left.key) {
tmp.left.parent = node;
}
tmp.parent = node.parent;
if (!node.parent) {
this.root = tmp;
}
else {
if (node === node.parent.left) {
node.parent.left = tmp;
}
else {
node.parent.right = tmp;
}
}
tmp.left = node;
node.parent = tmp;
}