红黑树 (Red-Black Tree)
本文作为红黑树系列的第三篇文章,重点介绍如何删除红黑树中的特定结点。
删除方法的实现难点在于:删除结点之后可能会对红黑树的结构产生冲击,造成删除结点之后的树不再符合红黑树特性。为描述方便在正式分析之前引入了一些自定义的术语使逻辑描述更加清晰。
1. 补充推论
-
- 红色结点不可能只有一个子结点;只有一个子结点的结点必为黑色。
-
- 删掉红色无子结点不会破坏红黑树的结构。
-
- 如果结点为黑色并且无子,则其兄弟结点一定也为黑,并且如果兄弟结点有子必为红。
2. 补充术语
- 拐:描述的是某个结点N相对于其父结点的位置,假如某个结点时左/右子结点,但是其父为右/左子结点,则将N称为拐结点;
- 顺:描述的是某个结点N相对于其父结点的位置,假如某个结点时左/右子结点,并且其父也为左/右子结点,则将N称为顺结点。
3. 删除的策略
- 整体策略如下:不同于插入结点的逻辑(先插入后调整),删除一个结点的时候需要先调整结点之间的结构然后再删除对象结点。
- 所以将删除一个结点分成两个大的部分,Ⅰ:位置调整,Ⅱ:删除结点。
4. 位置调整方法
针对待删除结点的周围进行结构调整的时候涉及到的结点范围如下所示:
-
- N: 待输出的结点
-
- B: N的兄弟结点,如果有的话
-
- P: N的父结点
-
- S: N的后继
5. 可能遇到的所有情况
- N度为2;
- N度为1。
- N度为0:
- N为root
- N为红
- N为黑
- P为黑
- B有拐结点
- B只有顺结点
- B无子结点
- P为红
- B有拐结点
- B只有顺结点
- B无子结点
- P为黑
检查一下,不重不漏,刚刚好!
5. 各个方法分析及解决
- 此时找到S(前驱也可以),然后将S的key和value赋值一份给N,最后将删除N这个命题转嫁成为删除S;删除S可以重新调用此方法,并且一定会被下面所列情况处理。
- 此时N的颜色必为黑(见:推论1),唯一子结点必为红;只需用唯一子结点替代N并继承N的颜色(黑色)即可。
- 3.0 直接将树置空(因为这个时候树中只有一个元素); 3.1 直接删掉N就可以了(见:推论2); 3.2 B为黑色(见:推论3),根据P颜色: 3.2.1 P为黑(此时NBP全黑,又称:一坨黑) 1)B有拐结点:转B让其变顺,再转P让B上位;最后将B P 拐结点都涂成黑色; 2)B没有拐结点:转P让B上位,然后把B P 非拐全部染黑即可 3)B无子(只有这种情况下,删除造成的影响才会向上传播):B变红,不用旋转;然后将问题传递给更上层的结点。 3.2.2 N为黑色,P为红色 1)B有拐结点:转B让其变顺,再转P让B上位;将P涂成黑色;如果拐了还需将B染红,将子染黑; 2)B没有拐结点:转P让B上位,将P涂成黑色; 3)B无子:P变黑,B变红即可。
6. 代码实现
class Node {
constructor(key, value, color) {
this.key = key;
this.value = value;
this.color = color;
this.left = null;
this.right = null;
}
}
class RedBlackTree {
constructor() {
this.root = null;
}
delete(key) {
// 排除树本身是空的情况
if (this.root === null) {
return;
}
// 排除找不到的情况
let targetNode = this.findNode(key);
if (targetNode === null) {
return;
}
// 可能会用到的后继结点
let replacementNode = null; // 用于替代被删除节点的节点
// 第一种情况:目标节点度为2,找到其前驱或后继节点替代并递归删除
if (targetNode.left !== null && targetNode.right !== null) {
let successorNode = this.findSuccessor(targetNode);
targetNode.key = successorNode.key;
targetNode.value = successorNode.value;
// 将删除对象转嫁到了后继结点上
targetNode = successorNode;
}
// 第二种情况:目标节点度为1,直接用子节点替代并继承颜色
replacementNode = targetNode.left !== null ? targetNode.left : targetNode.right;
if (replacementNode !== null) {
replacementNode.parent = targetNode.parent;
if (targetNode.parent === null) {
// 删除的结点是根节点的情况
this.root = replacementNode;
} else if (targetNode === targetNode.parent.left) {
targetNode.parent.left = replacementNode;
} else {
targetNode.parent.right = replacementNode;
}
if (targetNode.color === 'black') {
this.deleteFixup(replacementNode);
}
} else if (targetNode.parent === null) {
// 第三种情况:目标节点是根节点且无子节点,直接将树置空
this.root = null;
} else {
// 第四种情况:目标节点是叶子节点,删除并修复红黑树性质
if (targetNode.color === 'black') {
this.deleteFixup(targetNode);
}
// 此为删除目标结点
if (targetNode.parent !== null) {
if (targetNode === targetNode.parent.left) {
targetNode.parent.left = null;
} else if (targetNode === targetNode.parent.right) {
targetNode.parent.right = null;
}
targetNode.parent = null;
}
}
}
// 此函数才是处理复杂问题的核心技术
// *先占个位置,明天补上注释,今天要写吐了,红黑树真的难*
deleteFixup(node) {
while (node !== this.root && node.color === 'black') {
let siblingNode;
if (node === node.parent.left) {
siblingNode = node.parent.right;
if (siblingNode.color === 'red') {
siblingNode.color = 'black';
node.parent.color = 'red';
this.leftRotate(node.parent);
siblingNode = node.parent.right;
}
if (
(siblingNode.left === null || siblingNode.left.color === 'black') &&
(siblingNode.right === null || siblingNode.right.color === 'black')
) {
siblingNode.color = 'red';
node = node.parent;
} else {
if (siblingNode.right === null || siblingNode.right.color === 'black') {
siblingNode.left.color = 'black';
siblingNode.color = 'red';
this.rightRotate(siblingNode);
siblingNode = node.parent.right;
}
siblingNode.color = node.parent.color;
node.parent.color = 'black';
siblingNode.right.color = 'black';
this.leftRotate(node.parent);
node = this.root;
}
} else {
siblingNode = node.parent.left;
if (siblingNode.color === 'red') {
siblingNode.color = 'black';
node.parent.color = 'red';
this.rightRotate(node.parent);
siblingNode = node.parent.left;
}
if (
(siblingNode.left === null || siblingNode.left.color === 'black') &&
(siblingNode.right === null || siblingNode.right.color === 'black')
) {
siblingNode.color = 'red';
node = node.parent;
} else {
if (siblingNode.left === null || siblingNode.left.color === 'black') {
siblingNode.right.color = 'black';
siblingNode.color = 'red';
this.leftRotate(siblingNode);
siblingNode = node.parent.left;
}
siblingNode.color = node.parent.color;
node.parent.color = 'black';
siblingNode.left.color = 'black';
this.rightRotate(node.parent);
node = this.root;
}
}
}
node.color = 'black';
}
// 根据key找到要删除的结点,找不到返回null
findNode(key) {
let current = this.root;
while (current !== null) {
if (key < current.key) {
current = current.left;
} else if (key > current.key) {
current = current.right;
} else {
return current;
}
}
return null;
}
// 找到入参结点的后继结点
findSuccessor(node) {
if (node.right !== null) {
node = node.right;
while (node.left !== null) {
node = node.left;
}
return node;
}
let parent = node.parent;
while (parent !== null && node === parent.right) {
node = parent;
parent = parent.parent;
}
return parent;
}
// 绕入参结点左旋
leftRotate(node) {
let rightChild = node.right;
node.right = rightChild.left;
if (rightChild.left !== null) {
rightChild.left.parent = node;
}
rightChild.parent = node.parent;
if (node.parent === null) {
this.root = rightChild;
} else if (node === node.parent.left) {
node.parent.left = rightChild;
} else {
node.parent.right = rightChild;
}
rightChild.left = node;
node.parent = rightChild;
}
// 绕入参结点右旋
rightRotate(node) {
let leftChild = node.left;
node.left = leftChild.right;
if (leftChild.right !== null) {
leftChild.right.parent = node;
}
leftChild.parent = node.parent;
if (node.parent === null) {
this.root = leftChild;
} else if (node === node.parent.right) {
node.parent.right = leftChild;
} else {
node.parent.left = leftChild;
}
leftChild.right = node;
node.parent = leftChild;
}
}