第十二节:红黑树性质、相对平衡的原理、与AVL树的区别、手写红黑树
一. 红黑树简介
1. 背景
红黑树(英语:Red–black tree)是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构。
它在1972年由鲁道夫·贝尔发明,被称为“对称二叉B树”,它现代的名字源于Leo J. Guibas和罗伯特·塞奇威克于1978年写的一篇论文。
2. 性质
除了符合二叉搜索树的基本规则外, 还添加了一下特性:
(1). 节点是红色或黑色。
(2). 根节点是黑色。
(3). 每个叶子节点都是黑色的空节点(NIL节点,空节点)。
✓ 第三条性质要求每个叶节点(空节点)是黑色的
✓ 这是因为在红黑树中,黑色节点的数量表示从根节点到该节点的黑色节点数量。
(4) 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
✓ 第四条性质保证了红色节点的颜色不会影响树的平衡,同时保证了红色节点的出现不会导致连续的红色节点。
(5). 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
✓ 第五条性质是最重要的性质,保证了红黑树的平衡性。
二. 红黑树的相对平衡
1. 前面的性质约束,确保了红黑树的关键特性:
(1) 从根到叶子的最长可能路径, 不会超过最短可能路径的两倍长。
(2) 结果就是这个树基本是平衡的.
(3) 虽然没有做到绝对的平衡,但是可以保证在最坏的情况下, 依然是高效的。
2. 为什么可以做到 最长路径不超过最短路径的两倍 呢?
性质五决定了最短路径和最长路径必须有相同的黑色节点;
路径最短的情况:全部是黑色节点n;
路径最长的情况:首尾都是黑色节点n,中间全部是红色节点n – 1;
✓ 性质二:根节点是黑节点;
✓ 性质三:叶子节点都是黑节点;
✓ 性质四:两个红色节点不能相连
最短路径为 n – 1(边的数量);
最长路径为 (n + n – 1) - 1 = 2n – 2;
所以 最长路径 一定不超过 最短路径的2倍;
三. 性能分析
事实上,红黑树的性能在搜索上是不如AVL树的,为什么呢?
1. 我们来看一下右边的红黑树
(1) 首先,它符合是一颗红黑树吗?
符合。
(2) 这个时候我们插入 节点30,会被插入到哪里呢?
27的右边,并且节点30是红色节点时,依然符合红黑树的性质。 也就是对于红黑树来说,它不需要进行任何操作;
2. 那么AVL树会怎么样呢?
如果是AVL树必然要对17、25、27节点进行右旋转; 事实上右旋转是一系列的操作;
3. 但是红黑树的高度比AVL树要高。
所以如果同样是搜索30,那么红黑树需要搜索4次,AVL树搜索3次;
所以红黑树相当于牺牲了一点点的搜索性能,来提高了插入和删除的性能;
四. AVL树和红黑树对比
1. AVL树和红黑树的性能对比:
AVL树是一种平衡度更高的二叉搜索树,所以在搜索效率上会更高;
但是AVL树为了维护这种平衡性,在插入和删除操作时,通常会进行更多的旋转操作,所以效率相对红黑树较低;
红黑树在平衡度上相较于AVL树没有那么严格,所以搜索效率上会低一些;
但是红黑树在插入和删除操作时,通常需要更少的旋转操作,所以效率相对AVL树较高;
它们的搜索、添加、删除时间复杂度都是O(logn),但是细节上会有一些差异;
2. 开发中如何进行选择呢?
选择AVL树还是红黑树,取决于具体的应用需求。
如果需要保证每个节点的高度尽可能地平衡,可以选择AVL树。
如果需要保证删除操作的效率,可以选择红黑树。
3. 在早期的时候,很多场景会选择AVL树,目前选择红黑树的越来越多(AVL树依然是一种重要的平衡树)。
比如操作系统内核中的内存管理;
比如Java的TreeMap、TreeSet底层的源码;
五. 代码实操
定义红黑树的节点:定义一个带有键、值、颜色、左子节点、右子节点和父节点的类;
实现左旋操作:将一个节点向左旋转,保持红黑树的性质;
实现右旋操作:将一个节点向右旋转,保持红黑树的性质;
实现插入操作:在红黑树中插入一个新的节点,并保持红黑树的性质;
实现删除操作:从红黑树中删除一个节点,并保持红黑树的性质;
实现修复红黑树性质:在插入或删除操作后,通过旋转和变色来修复红黑树的性质;
其他方法较为简单,可以自行实现;
## 二. 定义红黑树的节点 使用 TypeScript 的泛型编写红黑树的节点。 ```ts enum Color { RED, BLACK, } class RedBlackNode<T> { value: T; color: Color; parent: RedBlackNode<T> | null; left: RedBlackNode<T> | null; right: RedBlackNode<T> | null; constructor( value: T, color: Color = Color.RED, parent: RedBlackNode<T> | null = null, left: RedBlackNode<T> | null = null, right: RedBlackNode<T> | null = null ) { this.value = value; this.color = color; this.parent = parent; this.left = left; this.right = right; } } ``` ## 三. 红黑树的结构封装 红黑树的整体结构: ```ts class RedBlackTree<T> { root: RedBlackNode<T> | null = null; // 查找某个节点再红黑树中的最小值 minimum(node: RedBlackNode<T> | null = this.root): RedBlackNode<T> | null { let current = node; while (current && current.left) { current = current.left; } return current; } // 查找红黑树中的某个节点 private search(value: T): RedBlackNode<T> | null { let node = this.root; let parent: RedBlackNode<T> | null = null; while (node) { if (node.value === value) { node.parent = parent; return node; } parent = node; if (value < node.value) { node = node.left; } else { node = node.right; } } return null; } } export {}; ``` ## 四. 红黑树的旋转操作 实现左旋转和有旋转操作: ```ts /** * 左旋操作 * * @param node 要进行左旋的结点 */ private leftRotate(node: RedBlackNode<T>) { // 获取 node 的右子节点 let rightChild = node.right!; // 将右子节点的左子节点赋值给 node 的右子节点 node.right = rightChild.left; // 如果右子节点的左子节点不为空,则将右子节点的左子节点的父节点指向 node if (rightChild.left) { rightChild.left.parent = node; } // 将右子节点的父节点指向 node 的父节点 rightChild.parent = node.parent; // 如果 node 的父节点为空,则将右子节点设为根结点 if (!node.parent) { this.root = rightChild; } // 如果 node 是它父节点的左子节点,则将右子节点设为 node 父节点的左子节点 else if (node === node.parent.left) { node.parent.left = rightChild; } // 否则,将右子节点设为 node 父节点的右子节点 else { node.parent.right = rightChild; } // 将 node 的父节点指向 rightChild,并将 rightChild 的左子节点指向 node rightChild.left = node; node.parent = rightChild; } /** * 右旋转 * @param node 旋转节点 */ private rightRotate(node: RedBlackNode<T>) { // 获取旋转节点的左子节点 let leftChild = node.left!; // 将旋转节点的左子节点的右子节点,接到旋转节点的左边 node.left = leftChild.right; // 如果左子节点的右子节点不为空,设置它的父节点为旋转节点 if (leftChild.right) { leftChild.right.parent = node; } // 将左子节点的父节点设为旋转节点的父节点 leftChild.parent = node.parent; // 如果旋转节点的父节点不存在,说明左子节点变成根节点 if (!node.parent) { this.root = leftChild; } else if (node === node.parent.right) { // 如果旋转节点是它父节点的右子节点,将父节点的右子节点设为左子节点 node.parent.right = leftChild; } else { // 如果旋转节点是它父节点的左子节点,将父节点的左子节点设为左子节点 node.parent.left = leftChild; } // 将旋转节点设为左子节点的右子节点 leftChild.right = node; // 将旋转节点的父节点设为左子节点 node.parent = leftChild; } ``` ## 五. 红黑树的插入操作 实现插入操作,并且插入后实现红黑树的平衡和保持性质: ```ts insert(value: T) { // 创建一个新节点 let newNode = new RedBlackNode(value); // 如果红黑树为空,将该节点作为根节点 if (!this.root) { this.root = newNode; // 根节点为黑色 newNode.color = Color.BLACK; return; } // 初始化搜索变量current和parent let current: RedBlackNode<T> | null = this.root; let parent: RedBlackNode<T> | null = null; // 搜索合适的插入位置 while (current) { parent = current; // 如果value小于当前节点,则继续往左子树搜索 if (value < current.value) { current = current.left; // 否则继续往右子树搜索 } else { current = current.right; } } // 将新节点的父节点设置为搜索到的父节点 newNode.parent = parent; // 将新节点插入到合适的位置 if (value < parent!.value) { parent!.left = newNode; } else { parent!.right = newNode; } // 修复插入导致的红黑树性质破坏 this.fixInsertion(newNode); } private fixInsertion(node: RedBlackNode<T>) { // 当父节点存在且颜色为红时 while (node.parent && node.parent.color === Color.RED) { // 获取祖父节点 let grandParent = node.parent.parent!; // 父节点是祖父节点的左子节点 if (node.parent === grandParent.left) { // 获取叔叔节点 let uncle = grandParent.right; // 叔叔节点存在且颜色为红 if (uncle && uncle.color === Color.RED) { // 将父节点颜色改为黑,叔叔节点颜色改为黑,祖父节点颜色改为红,node节点变为祖父节点,继续循环 node.parent.color = Color.BLACK; uncle.color = Color.BLACK; grandParent.color = Color.RED; node = grandParent; } else { // 当前节点是父节点的右子节点 if (node === node.parent.right) { // 将当前节点变为父节点,进行左旋操作 node = node.parent; this.leftRotate(node); } // 将父节点颜色改为黑,祖父节点颜色改为红,进行右旋操作 node.parent!.color = Color.BLACK; grandParent.color = Color.RED; this.rightRotate(grandParent); } } else { // 父节点是祖父节点的右子节点,与上面的同理 let uncle = grandParent.left; // 如果叔叔节点是红色的 if (uncle && uncle.color === Color.RED) { // 父节点设置为黑色 node.parent.color = Color.BLACK; // 叔叔节点设置为黑色 uncle.color = Color.BLACK; // 祖父节点设置为红色 grandParent.color = Color.RED; // 将当前节点设置为祖父节点 node = grandParent; } else { // 如果当前节点是父节点的左节点 if (node === node.parent.left) { // 将当前节点设置为父节点 node = node.parent; // 右旋父节点 this.rightRotate(node); } // 父节点设置为黑色 node.parent!.color = Color.BLACK; // 祖父节点设置为红色 grandParent.color = Color.RED; // 左旋祖父节点 this.leftRotate(grandParent); } } } // 根节点设置为黑色节点 this.root!.color = Color.BLACK; } ``` ## 六. 红黑树的删除操作 红黑树的删除操作和删除后的再平衡 ```ts /** * 删除红黑树中的某个节点 * * @param value 要删除的节点的值 */ delete(value: T) { // 先找到要删除的节点 const nodeToDelete = this.search(value); // 如果不存在,就直接退出 if (!nodeToDelete) { return; } // 否则删除节点 this._delete(nodeToDelete); } /** * 删除红黑树中的节点 * @param node 要删除的节点 */ private _delete(node: RedBlackNode<T>) { // 如果该节点同时存在左右节点,则找到右子树的最小节点作为该节点的后继 if (node.left && node.right) { const successor = this.minimum(node.right); node.value = successor!.value; node = successor!; } let child: RedBlackNode<T> | null; // 如果该节点存在左节点,则将该左节点作为它的唯一子节点 if (node.left) { child = node.left; } else if (node.right) { // 如果该节点存在右节点,则将该右节点作为它的唯一子节点 child = node.right; } else { child = null; } // 如果该节点没有子节点,直接删除 if (!child) { // 如果该节点是黑色,则需要特殊处理 if (node.color === Color.BLACK) { this._deleteCase1(node); } this._removeNode(node); } else { // 如果该节点是黑色,则需要特殊处理 if (node.color === Color.BLACK) { // 如果该节点的唯一子节点是红色,则将该唯一子节点设置为黑色 if (child.color === Color.RED) { child.color = Color.BLACK; } else { this._deleteCase1(node); } } // 用该节点的唯一子节点替换该节点 this._replaceNode(node, child); } } private _deleteCase1(node: RedBlackNode<T>) { // 如果有父节点,就进入 Case 2 if (node.parent) { this._deleteCase2(node); } } private _deleteCase2(node: RedBlackNode<T>) { // 找到兄弟节点 const sibling = this._sibling(node); // 如果兄弟节点存在且颜色为红色 if (sibling && sibling.color === Color.RED) { // 父节点颜色变为红色 node.parent!.color = Color.RED; // 兄弟节点颜色变为黑色 sibling.color = Color.BLACK; // 如果删除的节点是左子节点 if (node === node.parent!.left) { // 则向左旋转 this.leftRotate(node.parent!); } else { // 否则向右旋转 this.rightRotate(node.parent!); } } this._deleteCase3(node); } private _deleteCase3(node: RedBlackNode<T>) { const sibling = this._sibling(node); // 当父节点颜色是黑色,兄弟节点颜色是黑色,兄弟节点的左右子节点都是黑色 if ( node.parent!.color === Color.BLACK && sibling && sibling.color === Color.BLACK && (!sibling.left || sibling.left.color === Color.BLACK) && (!sibling.right || sibling.right.color === Color.BLACK) ) { // 将兄弟节点颜色设置为红色 sibling.color = Color.RED; // 递归处理父节点 this._deleteCase1(node.parent!); } else { // 进入下一个情况 this._deleteCase4(node); } } private _deleteCase4(node: RedBlackNode<T>) { const sibling = this._sibling(node); // 当父节点为红色,兄弟节点为黑色,且兄弟节点的左右子树为黑色时 if ( node.parent!.color === Color.RED && sibling && sibling.color === Color.BLACK && (!sibling.left || sibling.left.color === Color.BLACK) && (!sibling.right || sibling.right.color === Color.BLACK) ) { // 将兄弟节点涂红色 sibling.color = Color.RED; // 父节点涂黑色 node.parent!.color = Color.BLACK; } else { // 否则进入下一个删除 case this._deleteCase5(node); } } private _deleteCase5(node: RedBlackNode<T>) { const sibling = this._sibling(node); if (sibling && sibling.color === Color.BLACK) { // 如果当前节点是它父的左节点,并且兄弟节点的右节点存在且为红色 if ( node === node.parent!.left && sibling.right && sibling.right.color === Color.RED ) { // 将兄弟节点的颜色设置为红色 sibling.color = Color.RED; // 兄弟节点的右节点设置为黑色 sibling.right!.color = Color.BLACK; // 对兄弟节点进行左旋 this.leftRotate(sibling); } else if ( node === node.parent!.right && sibling.left && sibling.left.color === Color.RED ) { // 同上 sibling.color = Color.RED; sibling.left!.color = Color.BLACK; this.rightRotate(sibling); } } this._deleteCase6(node); } private _deleteCase6(node: RedBlackNode<T>) { const sibling = this._sibling(node); // 将兄弟节点颜色设置成父节点颜色 sibling!.color = node.parent!.color; // 将父节点颜色设置成黑色 node.parent!.color = Color.BLACK; if (node === node.parent!.left) { // 将兄弟节点的右子节点颜色设置成黑色 sibling!.right!.color = Color.BLACK; // 对父节点左旋 this.leftRotate(node.parent!); } else { // 将兄弟节点的左子节点颜色设置成黑色 sibling!.left!.color = Color.BLACK; // 对父节点右旋 this.rightRotate(node.parent!); } } private _removeNode(node: RedBlackNode<T>) { if (!node.parent) { this.root = null; } else if (node === node.parent.left) { node.parent.left = null; } else { node.parent.right = null; } } private _replaceNode(oldNode: RedBlackNode<T>, newNode: RedBlackNode<T>) { if (!oldNode.parent) { this.root = newNode; } else if (oldNode === oldNode.parent.left) { oldNode.parent.left = newNode; } else { oldNode.parent.right = newNode; } newNode.parent = oldNode.parent; } private _sibling(node: RedBlackNode<T>) { if (!node.parent) { return null; } return node === node.parent.left ? node.parent.right : node.parent.left; } ```
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。