双旋操作本质是Zig-Zig和Zig-Zag两类连续旋转组合,非单次操作:Zig-Zig需先转父-祖父再转x-父,Zig-Zag则先转x-父再转x-祖父,目标是使x逐层上移至根。

双旋操作的本质是分情况处理 Zig-Zig 和 Zig-Zag
伸展树(Splay Tree)里没有“单靠一次双旋就上根”的通用操作;所谓双旋,其实是 splay 过程中连续两次旋转的组合,目标是把目标节点 x 快速抬升,最终在若干轮后成为根。真正起作用的是三类基础旋转:Zig(x 是左/右孩子且父节点是根)、Zig-Zig(x 和父节点同为左或同为右孩子)、Zig-Zag(x 和父节点方向相反)。其中 Zig-Zig 和 Zig-Zag 就是常说的“双旋”场景。
Zig-Zig:父与祖父同向时,先转父再转 x
比如 x 是 p 的左孩子,p 又是 g(祖父)的左孩子。此时不能直接对 x 和 g 旋转——它们不相邻。必须先对 p 和 g 做一次右旋(Zig),让 p 上位;再对 x 和 p 做一次右旋(Zig)。两步合称 Zig-Zig。注意:两次都是同向旋转(此处均为右旋),但旋转对象不同。
常见错误是写成“先转 x 再转 p”,这会破坏 BST 性质。正确顺序必须是:先转 p-g,再转 x-p。
关键点:
立即学习“C++免费学习笔记(深入)”;
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
-
Zig-Zig要求x、p、g三点共线(都在左链或都在右链) - 两次旋转方向相同,但旋转轴分别是
g和p - 旋转后
x上升两层,g成为x的右子(若原为左链)
Zig-Zag:父与祖父反向时,先转 x 再转 x
比如 x 是 p 的右孩子,而 p 是 g 的左孩子。这时 x 和 g 直接构成一个“之”字形。标准做法是:先对 x 和 p 左旋(Zig),再对 x 和 g 右旋(Zag)——注意两次旋转都以 x 为新根,所以 x 在第二步仍能参与旋转。
容易踩的坑:
- 误以为要先转
p-g:那会把p提到g位置,反而让x更远离根 - 忽略旋转后子树重挂:Zig-Zag 后,原
p的右子(即x的左子)要重新挂到p下,否则丢失节点 - 方向判断混淆:用
p->left == x判断x是左孩子,别用值比较
完整 splay 操作不是只做一次双旋
调用 splay(root, x) 后,实际执行的是循环:只要 x != root,就根据 x 与父、祖父的关系选择 Zig / Zig-Zig / Zig-Zag。可能经历多次双旋,也可能夹杂一次 Zig(当 x 的父就是根时)。没有“一步双旋到根”的魔法,只有持续局部调整。
性能上,单次 splay 最坏 O(n),但均摊 O(log n);实现时务必检查空指针——尤其是访问 g->parent 前要确认 g 非空且非根。
最易被忽略的是边界:当 x 已是根,或父是根(只需一次 Zig),就不要再强行套用双旋逻辑。硬编码“必须双旋”会导致指针错乱或无限循环。

















