一文详解红黑树
一文详解红黑树
- 前言
- 一、基本概念与特性
- 1.1 红黑树的定义
- 1.2 红黑树的特性
- 二、红黑树的节点结构与表示
- 三、红黑树的基本操作
- 3.1 插入操作
- 3.2 删除操作
- 四、红黑树的应用场景
- 4.1 编程语言的集合类
- 4.2 数据库索引
- 4.3 Linux 内核进程调度
- 总结
前言
在数据结构的领域中,红黑树是一种高效且实用的自平衡二叉搜索树。它在保证二叉搜索树基本特性的同时,通过一系列规则维持树的平衡,从而确保插入、删除、查找等操作在平均和最坏情况下都能保持高效。红黑树广泛应用于编程语言的集合类、数据库索引、Linux 内核进程调度等场景。本文我将深入探讨红黑树的原理、结构特性、操作实现以及实际应用,结合代码示例,帮助读者全面掌握这一重要的数据结构。
一、基本概念与特性
1.1 红黑树的定义
红黑树是一种特殊的二叉搜索树,它的每个节点都被赋予了颜色属性,非红即黑。红黑树通过对节点颜色的约束以及特定的旋转和重新着色操作,来维持树的大致平衡,从而避免二叉搜索树在极端情况下退化为链表,保证操作的高效性。
1.2 红黑树的特性
红黑树必须满足以下五条特性
:
-
节点颜色属性:每个节点要么是红色,要么是黑色。
-
根节点属性:根节点是黑色。
-
叶子节点属性:每个叶子节点(NIL 节点,即空节点)是黑色。这里的叶子节点不是传统意义上的叶子节点,而是为了满足红黑树规则引入的虚拟节点。
-
红色节点约束:如果一个节点是红色的,那么它的两个子节点都是黑色的,即不存在连续的红色节点。
-
路径黑色节点数:从任意一个节点到其可达的叶子节点的所有路径上,包含相同数目的黑色节点。这个特性保证了红黑树的大致平衡,任何一条路径的长度都不会超过其他路径长度的两倍。
这些特性使得红黑树在插入和删除节点时,通过有限的旋转和颜色调整操作,就能重新恢复树的平衡状态。
二、红黑树的节点结构与表示
在实现红黑树时,首先需要定义节点结构。以 C++ 语言为例,红黑树的节点结构可以定义如下:
// 红黑树节点结构
struct RBNode {int key; // 节点存储的关键字bool color; // 节点颜色,true为红色,false为黑色RBNode* left;RBNode* right;RBNode* parent;RBNode(int k) : key(k), color(true), left(nullptr), right(nullptr), parent(nullptr) {}
};
在上述代码中,每个节点包含关键字key
、颜色属性color
、指向左子节点的指针left
、指向右子节点的指针right
以及指向父节点的指针parent
。初始时,新插入的节点颜色设为红色,这是因为将新节点设为红色更有可能保持红黑树的特性,减少调整操作。
三、红黑树的基本操作
3.1 插入操作
红黑树的插入操作基于二叉搜索树的插入,在插入新节点后,需要通过重新着色和旋转操作来恢复红黑树的特性。插入操作的主要步骤如下:
-
执行二叉搜索树插入:按照二叉搜索树的规则,将新节点插入到合适的位置。
-
调整树的平衡:新节点插入后,可能会破坏红黑树的特性,需要进行调整。如果新插入节点的父节点是黑色,那么红黑树的特性依然满足,无需调整;如果父节点是红色,则需要通过重新着色和旋转操作来恢复特性。
以下是插入操作的 C++ 代码实现:
// 左旋操作
void leftRotate(RBNode*& root, RBNode* x) {RBNode* y = x->right;x->right = y->left;if (y->left != nullptr) {y->left->parent = x;}y->parent = x->parent;if (x->parent == nullptr) {root = y;} else if (x == x->parent->left) {x->parent->left = y;} else {x->parent->right = y;}y->left = x;x->parent = y;
}// 右旋操作
void rightRotate(RBNode*& root, RBNode* x) {RBNode* y = x->left;x->left = y->right;if (y->right != nullptr) {y->right->parent = x;}y->parent = x->parent;if (x->parent == nullptr) {root = y;} else if (x == x->parent->right) {x->parent->right = y;} else {x->parent->left = y;}y->right = x;x->parent = y;
}// 插入后修复红黑树特性
void insertFixup(RBNode*& root, RBNode* z) {while (z != root && z->parent->color == true) {if (z->parent == z->parent->parent->left) {RBNode* y = z->parent->parent->right;if (y != nullptr && y->color == true) {z->parent->color = false;y->color = false;z->parent->parent->color = true;z = z->parent->parent;} else {if (z == z->parent->right) {z = z->parent;leftRotate(root, z);}z->parent->color = false;z->parent->parent->color = true;rightRotate(root, z->parent->parent);}} else {RBNode* y = z->parent->parent->left;if (y != nullptr && y->color == true) {z->parent->color = false;y->color = false;z->parent->parent->color = true;z = z->parent->parent;} else {if (z == z->parent->left) {z = z->parent;rightRotate(root, z);}z->parent->color = false;z->parent->parent->color = true;leftRotate(root, z->parent->parent);}}}root->color = false;
}// 插入节点
void insert(RBNode*& root, int key) {RBNode* z = new RBNode(key);RBNode* y = nullptr;RBNode* x = root;while (x != nullptr) {y = x;if (z->key < x->key) {x = x->left;} else {x = x->right;}}z->parent = y;if (y == nullptr) {root = z;} else if (z->key < y->key) {y->left = z;} else {y->right = z;}insertFixup(root, z);
}
3.2 删除操作
红黑树的删除操作比插入操作更为复杂,同样需要在删除节点后通过重新着色和旋转操作来恢复红黑树的特性。删除操作的主要步骤如下:
-
执行二叉搜索树删除:按照二叉搜索树的删除规则,找到要删除的节点,并将其从树中移除。
-
调整树的平衡:删除节点后,可能会破坏红黑树的特性,需要进行调整。根据被删除节点及其子节点的颜色情况,通过一系列的旋转和重新着色操作来恢复特性。
以下是删除操作的 C++ 代码实现(部分关键函数):
// 找到节点x的后继节点
RBNode* minimum(RBNode* x) {while (x->left != nullptr) {x = x->left;}return x;
}// 替换节点u和v
void transplant(RBNode*& root, RBNode* u, RBNode* v) {if (u->parent == nullptr) {root = v;} else if (u == u->parent->left) {u->parent->left = v;} else {u->parent->right = v;}v->parent = u->parent;
}// 删除后修复红黑树特性
void deleteFixup(RBNode*& root, RBNode* x) {while (x != root && x->color == false) {if (x == x->parent->left) {RBNode* w = x->parent->right;if (w->color == true) {w->color = false;x->parent->color = true;leftRotate(root, x->parent);w = x->parent->right;}if (w->left->color == false && w->right->color == false) {w->color = true;x = x->parent;} else {if (w->right->color == false) {w->left->color = false;w->color = true;rightRotate(root, w);w = x->parent->right;}w->color = x->parent->color;x->parent->color = false;w->right->color = false;leftRotate(root, x->parent);x = root;}} else {// 与x是左子节点的情况对称}}x->color = false;
}// 删除节点
void deleteNode(RBNode*& root, int key) {RBNode* z = root;while (z != nullptr && z->key != key) {if (key < z->key) {z = z->left;} else {z = z->right;}}if (z == nullptr) return;RBNode* y = z;bool yOriginalColor = y->color;RBNode* x;if (z->left == nullptr) {x = z->right;transplant(root, z, z->right);} else if (z->right == nullptr) {x = z->left;transplant(root, z, z->left);} else {y = minimum(z->right);yOriginalColor = y->color;x = y->right;if (y->parent == z) {x->parent = y;} else {transplant(root, y, y->right);y->right = z->right;y->right->parent = y;}transplant(root, z, y);y->left = z->left;y->left->parent = y;y->color = z->color;}if (yOriginalColor == false) {deleteFixup(root, x);}delete z;
}
四、红黑树的应用场景
4.1 编程语言的集合类
在 Java 中,TreeMap
和TreeSet
的底层实现就是红黑树。它们利用红黑树的特性,保证了元素的有序存储,并且在插入、删除和查找操作上都具有较好的性能,时间复杂度均为 O ( log n ) O(\log n) O(logn),其中n
是集合中元素的个数。这使得它们在需要对元素进行排序和快速查找的场景中非常适用,例如统计单词出现频率并按字母顺序输出等。
4.2 数据库索引
在数据库系统中,红黑树可以用于实现索引结构。通过将数据按照特定的关键字构建成红黑树,能够快速定位到需要查询的数据记录,提高数据的查询效率。与哈希索引相比,红黑树索引不仅支持快速的等值查询,还支持范围查询,在很多场景下具有独特的优势。
4.3 Linux 内核进程调度
在 Linux 内核的进程调度算法中,也使用了红黑树来管理进程。红黑树用于存储进程的相关信息,如进程的优先级、运行时间等。通过红黑树的高效操作,内核可以快速找到需要调度的进程,实现公平且高效的进程调度。
总结
红黑树作为一种自平衡二叉搜索树,通过严格的节点颜色约束和巧妙的旋转、重新着色操作,在保证二叉搜索树基本功能的同时,维持了树的大致平衡,确保了插入、删除、查找等操作的高效性。从节点结构的定义到插入删除操作的实现,从理论原理到实际应用场景,红黑树都展现出了强大的功能和广泛的适用性。掌握红黑树的原理和应用,对于深入理解数据结构和算法,以及解决实际编程问题都具有重要意义。在后续的学习和实践中,我们还可以进一步探索红黑树与其他数据结构、算法的结合,发挥其更大的价值。
That’s all, thanks for reading!
创作不易,点赞鼓励;
知识无价,收藏备用;
持续精彩,关注不错过!