C++ 伸展树与红黑树详解及实现
在之前的讨论中,我们了解了 AVL 树的实现。接下来我们将深入探讨 C++ 中的两种自平衡二叉搜索树:伸展树(Splay Tree)与红黑树(Red-Black Tree)。这两类结构在工程实践中各有不可替代的价值。
1. 伸展树介绍
伸展树是一种改进的二叉搜索树,由 John Edward Hopcroft 和 Robert Endre Tarjan 于 1985 年共同发明。它属于自调整数据结构,与 AVL 树不同,伸展树不追求严格的平衡,而是利用'局部性原理'优化频繁访问的场景。
核心思想
- 单一旋转:将经常访问的节点上移,使其靠近根节点。
- 移动到根部:假设当前访问的节点会再次被高频访问,通过反复旋转将其移至根节点。
每当执行搜索、插入或删除操作时,伸展树都会对目标节点执行一次'展开'(Splaying)过程,将其移至根节点。如果删除节点,则将其父节点展开到根节点。
旋转类型
一次'展开'由一组旋转组成,主要包括三种情况:
- 单旋转:当被访问节点的父结点是根结点时执行。
- 一字形旋转:当节点与其父、祖父结点呈同构形状(如左 -左或右 -右)时执行的双旋转。
- 之字形旋转:当节点与其父、祖父结点呈异构形状(如左 -右或右 -左)时执行的双旋转。
这些操作使得访问最频繁的结点靠近树结构的根部,从而减少平均访问代价。虽然单次操作可能花费 O(n) 时间,但在 m >= n 次操作的序列中,总时间复杂度为 O(m log₂n),均摊时间复杂度为 O(log₂n)。
// 伸展算法伪代码描述
void splaying(Node* g, Node* p, Node* s) {
// g 是 p 的父结点,p 是 s 的父结点。算法将 s 移到根结点位置
while (s != root) {
if (s->parent == root) {
// 单旋转
rotate(s);
} else if ((s->parent->left == s && s->grandfather->left == s->parent) ||
(s->parent->right == s && s->grandfather->right == s->parent)) {
// 一字形双旋转
zig_zig(s);
} else {
// 之字形双旋转
zig_zag(s);
}
}
}
2. 红黑树介绍
2.1 红黑树的概念
红黑树是一棵二叉搜索树,每个节点增加一个存储位表示颜色(红色或黑色)。通过对路径颜色的约束,确保没有一条路径比其他路径长出两倍,从而实现近似平衡。
2.2 红黑树的性质
- 根结点和所有外部结点(NIL)的颜色是黑色。
- 从根结点到外部结点的途中没有连续两个结点的颜色是红色。
- 所有从根到外部结点的路径上都有相同数目的黑色结点。
基于这些性质,可以推导出红黑树的高度 h 与节点数 n 的关系:h ≤ 2 log₂(n+1)。这意味着搜索、插入、删除操作的时间复杂度均为 O(log₂n)。


