学习记录
红黑树的性质:(随便百度一下就可以查到)
1.每个节点要么是黑色, 要么是红色。
2.根节点是黑色
3.所有叶子节点是黑色(在红黑树中, 我们将底层叶节点看做有两个值为 NULL 的孩子节点, 以 NULL 节点作为红黑树的叶节点)
4.每个红色节点的两个子节点一定都是黑色。(但黑色节点的子节点可以也是黑色)
5.对于树中的任意一个结点, 从该节点到所有叶结点的路径上一定包含相同数量的黑节点(我们将红黑树任意节点到叶子节点的路径上的黑色节点的个数称为该节点的 「黑高」)
因为二叉搜索树在插入有序数据时,会退化成链表,复杂度变为O(N),所以引入自平衡的红黑树,其插入,修改,删除时间复杂度都是O(Log(N))。
在学习的过程中,觉得要了解红黑树,首先要弄清楚节点的旋转操作(旋转操作可以为一个方向上带来1或2个节点,相应的另一个方向会减少1或2个节点,这是平衡的关键),然后对于比较复杂的删除逻辑,我认为可以首先分为三种情况:
1.删除的节点是根节点;
2.删除的节点不是黑色叶子节点;
3.删除的节点是黑色叶子结点。
对于情况1,可以看下面的代码逻辑(按子节点数量分为三个处理逻辑)
对于情况2,比较好处理一些,可以先判断删除的是不是叶子节点,如果不是,则可以通过简化逻辑,使待删除的节点变为叶子结点(比如:待删除节点有一个子节点,那可以把它的值用子节点的值替换,然后进行递归操作,代码中有注释),然后如果待删除节点是红色叶子节点,那可以直接删除,对红黑树结构不会造成影响。
对于情况3,算是删除的核心逻辑了,因为删除黑色节点会打破红黑树的第五条性质,所以必须对树重新进行平衡操作
情况3可以继续细分成多种情况,下面一一说明
(首先设定:
>X是待删除黑色叶子节点
P是X节点的父节点
S是X节点的兄弟节点
SL是兄弟节点的左子节点
SR是兄弟节点的右子节点
以下为了方便表述,我假设X是P的右子节点!!!
X是左子节点的话其实就是把操作反过来就好了)
3.1:S节点是红色,SL、SR都存在且是黑色子节点
操作:S节点染成P节点的颜色,SR节点染成红色,旋转P节点
3.2:S节点是黑色,SL、SR都存在且是红色子节点,P节点可红可黑
操作:SL变成S的颜色,S变成P的颜色,P变成X的颜色,然后旋转P节点(旋转后P接替了X的位置,S节点接替了P节点的位置,SL节点接替了S节点的位置,SR是红色节点,不影响黑高)
3.3:S节点是黑色,且S节点有且仅有红色左子节点(SL存在,SR为空),P节点可红可黑
操作:SL变成S的颜色,S变成P的颜色,P变成X的颜色,然后旋转P节点(旋转后的位置变化:P->X, S->P, SL->S)
3.4:S节点是黑色,且S节点有且仅有红色右子节点(SR存在,SL为空)
操作:先旋转S节点(旋转后就变成了3.3的情况),然后SR变成P的颜色,P变成X的颜色,然后旋转P节点(旋转后的位置变化:S->S, SR->P, P->X)
3.5:S节点是黑色,P节点是红色,且S节点没有子节点(SL,SR都为空)
操作:S节点变红,P节点变黑
3.6:S节点是黑色,P节点是黑色,且S节点没有子节点(SL,SR都为空)
操作:S节点变红,此时树P平衡了,但是经过P节点的路径的黑高整体减少了1,这时需要假设P节点为黑色待删除节点,继续向上迭代处理,直到ROOT节点。
import { BinaryTree, BinaryTreeNode } from "./BinaryTree";
export enum RBTColor {
RED,
Black
}
export type Direction = 'leftNode' | 'rightNode';
export class RedBlackTreeNode<T>{
public parent: RedBlackTreeNode<T>;
public leftNode: RedBlackTreeNode<T>;
public rightNode: RedBlackTreeNode<T>;
public key: number;
public value: T;
public color: RBTColor;
constructor(key, value?, color = RBTColor.RED) {
this.key = key;
this.value = value;
this.leftNode = null;
this.rightNode = null;
this.color = color;
}
public get childNum(): number {
let count = 0;
this.leftNode && count++;
this.rightNode && count++;
return count;
}
}
export class RedBlackTree<T>{
public root: RedBlackTreeNode<T>;
public preOrder(node: RedBlackTreeNode<T>): void {
if (!node) return;
console.log(node);
this.preOrder(node.leftNode);
this.preOrder(node.rightNode);
}
public treeNodes = {};
public inOrder(node: RedBlackTreeNode<T>, ceng: number = 0): void {
if (!node) return;
ceng++;
this.inOrder(node.leftNode, ceng);
let nodeInfo = `${node.key}-${node.color == RBTColor.RED ? '红色' : '黑色'}`;
if (this.treeNodes[ceng]) {
this.treeNodes[ceng].push(nodeInfo);
} else {
this.treeNodes[ceng] = [nodeInfo];
}
// console.log(`第${ceng}层>>>`, node.key, node.color == RBTColor.RED ? '红色' : '黑色');
this.inOrder(node.rightNode, ceng);
}
public testConsole(): void {
this.treeNodes = {};
this.inOrder(this.root);
console.log(this.treeNodes);
}
public search(key: number): RedBlackTreeNode<T> {
let curNode = this.root;
while (curNode) {
if (key < curNode.key) {
curNode = curNode.leftNode;
} else if (key > curNode.key) {
curNode = curNode.rightNode;
} else {
return curNode;
}
}
return null;
}
public insert(key, value?) {
let newNode: RedBlackTreeNode<T> = new RedBlackTreeNode(key, value);
if (!this.root) {
newNode.color = RBTColor.Black;
this.root = newNode;
} else {
let curNode = this.root;
while (curNode) {
if (key < curNode.key) {
if (curNode.leftNode) {
curNode = curNode.leftNode;
} else {
curNode.leftNode = newNode;
newNode.parent = curNode;
break;
}
} else if (key > curNode.key) {
if (curNode.rightNode) {
curNode = curNode.rightNode;
} else {
curNode.rightNode = newNode;
newNode.parent = curNode;
break;
}
} else {
curNode.value = value;
break;
}
}
//插入结束,处理颜色
this.adjust(newNode);
}
}
//处理节点
private adjust(node: RedBlackTreeNode<T>): void {
if (this.root === node) {//节点是root节点,则直接变黑即可
node.color = RBTColor.Black;
return;
}
if (node.color == RBTColor.Black || node.parent.color == RBTColor.Black) return;//本节点或父节点是黑色,不用处理
//以下的逻辑,父节点一定是红色
let PNode: RedBlackTreeNode<T> = node.parent;//父节点-一定存在
let GNode: RedBlackTreeNode<T> = PNode.parent;//祖父节点-一定存在
let UNode: RedBlackTreeNode<T> = PNode.key < GNode.key ? GNode.rightNode : GNode.leftNode;//叔叔节点-可能不存在
//判断叔叔节点的颜色,如果是红色则和父节点一同变为黑色,同时把祖父节点变为红色
if (UNode && UNode.color == RBTColor.RED) {
PNode.color = UNode.color = RBTColor.Black;
GNode.color = RBTColor.RED;
this.adjust(GNode);
return;
}
//这里就要判断节点与父节点方向一至
let nodeDir: Direction = node.key < PNode.key ? 'leftNode' : 'rightNode';
let PNodeDir: Direction = PNode.key < GNode.key ? 'leftNode' : 'rightNode';
if (nodeDir == PNodeDir) {
//祖父节点与父节点颜色调换
GNode.color = PNode.color;
PNode.color = RBTColor.Black;
this.rotation(GNode, PNode);
this.adjust(PNode);
} else {
//如果方向不一致,则对父节点进行旋转,将结构变成父子节点方向一至的情况
this.rotation(PNode, node);
this.adjust(PNode);
}
}
//旋转
private rotation(parent: RedBlackTreeNode<T>, child: RedBlackTreeNode<T>) {
/**判断左右旋 */
if (child.key < parent.key) {
//parent右旋
this.rRotation(parent, child);
} else {
//parent左旋
this.lRotation(parent, child);
}
}
/**左旋 */
private lRotation(parent: RedBlackTreeNode<T>, child: RedBlackTreeNode<T>) {
if (child.parent !== parent) return;
let GNode: RedBlackTreeNode<T>;//祖父节点
let direction: Direction;//parent节点相对于祖父节点的方向
if (this.root != parent) {
GNode = parent.parent;
direction = GNode.key > parent.key ? 'leftNode' : 'rightNode';
}
parent.rightNode = child.leftNode;
parent.rightNode && (parent.rightNode.parent = parent);
child.leftNode = parent;
child.leftNode.parent = child;
if (GNode) {
GNode[direction] = child;
GNode[direction].parent = GNode;
} else {
this.root = child;
child.parent = null;
}
}
/**右旋 */
private rRotation(parent: RedBlackTreeNode<T>, child: RedBlackTreeNode<T>) {
let GNode: RedBlackTreeNode<T>;//祖父节点
let direction: Direction;//parent节点相对于祖父节点的方向
if (this.root != parent) {
GNode = parent.parent;
direction = GNode.key > parent.key ? 'leftNode' : 'rightNode';
}
parent.leftNode = child.rightNode;
parent.leftNode && (parent.leftNode.parent = parent);
child.rightNode = parent;
child.rightNode.parent = child;
if (GNode) {
GNode[direction] = child;
GNode[direction].parent = GNode;
} else {
this.root = child;
child.parent = null;
}
}
public delete(key: number): RedBlackTreeNode<T> {
let node = this.search(key);
if (!node) return null;
this.deleteNode(node, this.getDirection(node));
this.testConsole();
}
private getDirection(node: RedBlackTreeNode<T>): Direction {
if (!node.parent) return null;
return node.key < node.parent.key ? 'leftNode' : 'rightNode';
}
private deleteNode(node: RedBlackTreeNode<T>, direction: Direction): RedBlackTreeNode<T> {
if (!node) return;
//删除的节点一定是叶子节点(如果不是,则可以变换成删除叶子节点的情况)
/**----------------非叶子节点简化逻辑--------------------------- */
if (node.childNum == 2) {
let lMaxNode = this.getMaxNode(node.leftNode);
direction = this.getDirection(lMaxNode);
this.copy(lMaxNode, node);
this.deleteNode(lMaxNode, direction);
return;
}
if (node.childNum == 1) {
let childNode = node.leftNode || node.rightNode;
direction = this.getDirection(childNode);
this.copy(childNode, node);
this.deleteNode(childNode, direction);
return;
}
/**----------------非叶子节点简化逻辑--------------------------- */
if (this.root == node) {
//待删除节点是根节点 树中只有一个根节点
this.root = null;
return;
}
//以下 待删除的节点一定是叶子节点,也不是根节点
let P = node.parent//待删除节点父节点
if (node.color == RBTColor.RED) {
//删除的是红色节点,直接删除没有任何影响
P[direction] = null;
return;
}
//以下的情况的是 删除的是黑色叶节点,需要进行平衡处理
P[direction] = null;
this.balance(node, direction);
}
/**
* 平衡处理(node一定是黑色节点)
* @param node 要处理的节点
*/
public balance(node: RedBlackTreeNode<T>, direction: Direction): void {
if (!node || node.color == RBTColor.RED || this.root == node) return;
let P = node.parent,//待删除节点父节点
S = direction == 'leftNode' ? P.rightNode : P.leftNode,//待删除节点兄弟节点
SL = S.leftNode,//兄弟节点左子节点
SR = S.rightNode;//兄弟节点右子节点
//以下的情况的是 删除的是黑色叶节点
if (S.color == RBTColor.RED) {
//兄弟节点是红色节点(也就是说,除了兄弟节点,其它节点都是黑色)
S.color = RBTColor.Black;
if (direction == 'rightNode') {
SR.color = RBTColor.RED;
} else {
SL.color = RBTColor.RED;
}
this.rotation(P, S);
return;
}
if (SL?.color == RBTColor.RED && SR?.color == RBTColor.RED) {
//兄弟节点有两个红色子节点
//父节点旋转之后,兄弟左子节点会顶替兄弟节点的位置,兄弟节点顶替父节点位置,父节点顶替待删除节点的位置,右子节点是红色无需处理
if (direction == 'rightNode') {
SL.color = S.color;
} else {
SR.color = S.color;
}
S.color = P.color;
P.color = node.color;
this.rotation(P, S);
return;
}
if (!SR && SL?.color == RBTColor.RED) {
//兄弟节点的左子节点是红色,右子节点不存在
if (direction == 'rightNode') {
SL.color = S.color;
S.color = P.color;
P.color = node.color;
this.rotation(P, S);
} else {
this.rotation(S, SL);
SL.color = P.color;
P.color = node.color;
this.rotation(P, SL);
}
return;
}
if (!SL && SR?.color == RBTColor.RED) {
//兄弟节点的右子节点是红色,左子节点不存在
if (direction == 'rightNode') {
this.rotation(S, SR);
SR.color = P.color;
P.color = node.color;
this.rotation(P, SR);//这里SR其实经过旋转变成了待删除节点的兄弟节点
} else {
SR.color = S.color;
S.color = P.color;
P.color = node.color;
this.rotation(P, S);
}
return;
}
if (S.childNum == 0) {
//兄弟节点没有子节点
let PColor = P.color;
S.color = RBTColor.RED;
P.color = RBTColor.Black;
if (PColor == RBTColor.Black) {
//经过变色后,经过父节点的路径的黑高会减少1,需要把父节点当做被删除的节点,向上继续递归操作
this.balance(P, this.getDirection(P));
}
}
}
/**
* 只进行数值拷贝,不进行真正的删除
* @param deleteNode 要待删除节点
* @returns 返回被拷贝的那个节点
*/
public preDelete(deleteNode: RedBlackTreeNode<T>): RedBlackTreeNode<T> {
if (deleteNode.childNum == 2) {
let lMaxNode = this.getMaxNode(deleteNode.leftNode);
this.copy(lMaxNode, deleteNode);
return lMaxNode;
} else if (deleteNode.childNum == 1) {
let childNode = deleteNode.leftNode || deleteNode.rightNode
this.copy(childNode, deleteNode);
return childNode;
} else {
return null;
}
}
/**
* 复制一个节点的值到另一个节点上
* @param fromNode 拷贝数据来源节点
* @param toNode 被赋值的节点
* @returns
*/
public copy(fromNode: RedBlackTreeNode<T>, toNode: RedBlackTreeNode<T>) {
if (!fromNode || !toNode) return;
toNode.key = fromNode.key;
toNode.value = fromNode.value;
}
public getMaxNode(node: RedBlackTreeNode<T>): RedBlackTreeNode<T> {
while (node.rightNode) {
node = node.rightNode;
}
return node;
}
public getMinNode(node: RedBlackTreeNode<T>): RedBlackTreeNode<T> {
while (node.leftNode) {
node = node.leftNode;
}
return node;
}
}