上一篇《TypeScript 数据结构与算法:AVL 树》实现了 Typescript
中自平衡的AVL树
的数据结构与算法,本篇继续进一步实现性能更加优秀的红黑树(red–black tree)
。
虽然 AVL 树
已经实现了二叉搜索树的平衡,但是由于每次添加和删除节点时都会涉及到再平衡操作,所以会比较复杂费时。所以在工业级应用中,都会用另一种近似平衡树算法
红黑树
来实现。
这次断更很长时间,就是因为红黑树花费我了大量的时间来学习和理解。这是至今为止遇到的最难写的数据结构的代码,虽然现在理解了原理,但是让我离线手写我仍然写不出来。而且红黑树和其他数据结构有个最大的区别:普通的数据结构都是设计
出来的,但是红黑树是由 2-3 树``推导
出来的数学模型。
具体的推导过程我就不把经典复述一遍了,感兴趣的同学可以看《算法第四版》的 269
页到 292
页,详细讲述了 2-3 树
如何实现自平衡
以及如何把 2-3 树
映射成等价的左倾红黑树
。值得一提的是,该书的作者 Robert Sedgewick 正是红黑树的发明者。学习完之后有几个深刻体会:
- 对于 2-3 树
等价
的红黑树来说,由于 2-3 树的三叉节点
被变为了 3
个二叉节点树
,所以红黑树中的红
颜色就代表着这里是 2-3 树中的三叉节点
。
AVL 树
的高度接近 log₂N
;对于 2-3 树
来说,由于节点可以为二叉
或三叉
,所以平衡后高度不会超过 log₂N
,也不会低于 log₃N
;而对于 2-3 树
等价的红黑树
来说,由于三叉节点
被变为了 2 层
,所以平衡后高度不会超过 2log₂N
。
- 很关键的一点,只要看见红黑树的
红色节点
(链接),别把它当成子节点,把它抻平,就能理解 2-3 树
和左倾红黑树
的对应关系,如下图所示:

另外,左倾红黑树
还有另外一些推导出来的性质:
红
链接均为左
链接;
没有
任何一个节点同时和两条红链接相连
(也就是说红色节点不能相邻
或者为兄弟节点
);
- 该树是
完美黑色平衡
的,即任意空链接到根结点的路径上的黑链接数量相同(也就是说把红色抻平后,各个叶子节点的高度相差不会超过 1
)。
具体的红黑树的算法实现过程我就不赘述了,按照我的理解把算法第四版的左倾红黑树 TypeScript
代码实现如下:
红黑树代码
节点类
红黑树节点类 RedBlackNode
继承自之前实现的二叉搜索树的节点类 Node
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import { Node } from "./node";
export enum Colors { RED = 0, BLACK = 1 }
export class RedBlackNode<K> extends Node<K> { left: RedBlackNode<K>; right: RedBlackNode<K>; color: Colors;
constructor(public key: K) { super(key); this.color = Colors.RED; }
isRed(): boolean { return this.color === Colors.RED; }
flipColor() { this.color = 1 ^ this.color; } }
|
红黑树类
同样,红黑树类 RedBlackTree
也继承自之前实现的二叉搜索树类 BinarySearchTree
。在实现 RedBlackTree
的代码时,参照的算法四中已经实现的红黑树类的 JAVA 代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
| import { defaultCompare, ICompareFunction, Compare } from "../util"; import BinarySearchTree from "./binary-search-tree"; import { RedBlackNode, Colors } from "./models/red-black-node";
export default class RedBlackTree<T> extends BinarySearchTree<T> { protected root: RedBlackNode<T>;
constructor(protected compareFn: ICompareFunction<T> = defaultCompare) { super(compareFn); }
private rotateRight(node: RedBlackNode<T>): RedBlackNode<T> { const tmp = node.left; node.left = tmp.right; tmp.right = node; tmp.color = node.color; node.color = Colors.RED; return tmp; }
private rotateLeft(node: RedBlackNode<T>): RedBlackNode<T> { const tmp = node.right; node.right = tmp.left; tmp.left = node; tmp.color = node.color; node.color = Colors.RED; return tmp; }
insert(key: T) { this.root = this.insertNode(this.root, key); this.root.color = Colors.BLACK; }
protected insertNode(node: RedBlackNode<T>, key: T): RedBlackNode<T> { if (node == null) { let node = new RedBlackNode(key); node.color = Colors.RED; return node; }
if (this.compareFn(key, node.key) === Compare.LESS_THAN) { node.left = this.insertNode(node.left, key); } else if (this.compareFn(key, node.key) === Compare.BIGGER_THAN) { node.right = this.insertNode(node.right, key); } else { node.key = key; }
return this.balance(node); }
public deleteMin() { if (this.root) return;
if (!this.isRed(this.root.left) && !this.isRed(this.root.right)) this.root.color = Colors.RED;
this.root = this.deleteMinNode(this.root); if (this.root) this.root.color = Colors.BLACK; }
private deleteMinNode(node: RedBlackNode<T>): RedBlackNode<T> { if (node.left == null) return null;
if (!this.isRed(node.left) && !this.isRed(node.left.left)) node = this.moveRedLeft(node);
node.left = this.deleteMinNode(node.left); return this.balance(node); }
public deleteMax() { if (!this.root) return;
if (!this.isRed(this.root.left) && !this.isRed(this.root.right)) this.root.color = Colors.RED;
this.root = this.deleteMaxNode(this.root); if (this.root) this.root.color = Colors.BLACK; }
private deleteMaxNode(node: RedBlackNode<T>): RedBlackNode<T> { if (this.isRed(node.left)) node = this.rotateRight(node);
if (node.right == null) return null;
if (!this.isRed(node.right) && !this.isRed(node.right.left)) node = this.moveRedRight(node);
node.right = this.deleteMaxNode(node.right);
return this.balance(node); }
public delete(key: T) { if (!this.search(key)) return;
if (!this.isRed(this.root.left) && !this.isRed(this.root.right)) this.root.color = Colors.RED;
this.root = this.deleteNode(this.root, key); if (this.root) this.root.color = Colors.BLACK; }
private deleteNode(node: RedBlackNode<T>, key: T): RedBlackNode<T> { if (this.compareFn(key, node.key) === Compare.LESS_THAN) { if (!this.isRed(node.left) && !this.isRed(node.left?.left)) node = this.moveRedLeft(node); node.left = this.deleteNode(node.left, key); } else { if (this.isRed(node.left)) node = this.rotateRight(node);
if ( this.compareFn(key, node.key) === Compare.EQUALS && node.right == null ) return null;
if (!this.isRed(node.right) && !this.isRed(node.right?.left)) node = this.moveRedRight(node);
if (this.compareFn(key, node.key) === Compare.EQUALS) { const x = this.minNode(node.right); node.key = x.key; node.right = this.deleteMinNode(node.right); } else { node.right = this.deleteNode(node.right, key); } } return this.balance(node); }
getRoot(): RedBlackNode<T> { return this.root; }
private flipColors(node: RedBlackNode<T>) { node.flipColor(); node.left.flipColor(); node.right.flipColor() }
private balance(node: RedBlackNode<T>): RedBlackNode<T> { if (this.isRed(node.right) && !this.isRed(node.left)) node = this.rotateLeft(node); if (this.isRed(node.left) && this.isRed(node.left?.left)) node = this.rotateRight(node); if (this.isRed(node.left) && this.isRed(node.right)) this.flipColors(node); return node; }
private moveRedLeft(node: RedBlackNode<T>): RedBlackNode<T> { this.flipColors(node); if (this.isRed(node.right.left)) { node.right = this.rotateRight(node.right); node = this.rotateLeft(node); this.flipColors(node); } return node; }
private moveRedRight(node: RedBlackNode<T>): RedBlackNode<T> { this.flipColors(node); if (this.isRed(node.left.left)) { node = this.rotateLeft(node); this.flipColors(node); } return node; }
private isRed(node: RedBlackNode<T>) { if (!node) { return false; } return node.isRed(); } }
|
终于把红黑树看完了,下一篇来分析 二叉堆
。
前端记事本,不定期更新,欢迎关注!