C++ 伸展树与红黑树详解及核心实现
1. 伸展树介绍
伸展树(Splay Tree)是一种自调整的二叉搜索树,由 John Edward Hopcroft 和 Robert Endre Tarjan 于 1985 年提出。与 AVL 树不同,伸展树不追求严格的平衡,而是利用'局部性原理',将最近访问的节点通过旋转操作移至根节点附近。
这种机制使得频繁访问的数据能更快被检索,虽然单次操作最坏情况可能达到 O(n),但在 m >= n 次操作的序列中,均摊时间复杂度为 O(log n)。伸展树非常适合缓存、热点数据查询等场景。
1.1 伸展操作
每当访问(搜索、插入或删除)一个节点 s 时,执行一次'伸展'过程,将其移至根部。根据节点 s 与其父节点 p、祖父节点 g 的位置关系,分为三种旋转类型:
- 单旋转:当 s 的父节点是根节点时执行。s 成为新根,p 变为子节点。
- 一字形旋转(Zig-Zig):s 与 p 同侧(如都是左子或都是右子)。先围绕 p 旋转 g,再围绕 s 旋转 p。
- 之字形旋转(Zig-Zag):s 与 p 异侧(如 s 是 p 的左子,p 是 g 的右子)。先围绕 s 旋转 p,再围绕 s 旋转 g。
// 伸展算法伪代码逻辑
void splaying(Node* g, Node* p, Node* s) {
// g 是 p 的父结点,p 是 s 的父结点。算法将 s 移到根结点位置
while (s != root) {
if (p == root) {
// 情况 1: 单旋转
rotate(p, s);
} else if ((g->left == p && p->left == s) || (g->right == p && p->right == s)) {
// 情况 2: 一字形双旋转
rotate(g, p);
rotate(p, s);
} else {
// 情况 3: 之字形双旋转
rotate(s, p);
rotate(s, g);
}
}
}
伸展树的插入与删除操作遵循二叉搜索树规则,但插入后需立即将新节点伸展至根,删除时则将父节点伸展至根。相比 AVL 树,伸展树的调整频率与访问热度相关,动态适应性更强。
2. 红黑树介绍
2.1 红黑树的概念
红黑树是一棵二叉搜索树,每个节点增加颜色属性(红色或黑色)。通过对路径上颜色的约束,确保没有一条路径比其他路径长出一倍以上,从而实现近似平衡。它常被用于 C++ STL 中的 std::map、std::set 等容器的底层实现。
2.2 红黑树的性质
- 根节点和所有外部节点(NIL)的颜色是黑色。
- 从根到叶子的路径上,不能有两个连续的红色节点。
- 所有从根到外部节点的路径上,包含相同数目的黑色节点(黑高度)。
若设根节点的黑高度为 r,则红黑树的高度 h 满足 $h \leqslant 2r$。这意味着红黑树的查找、插入、删除操作在最坏情况下时间复杂度均为 $O(\log_2 n)$。


