二叉排序树可以支持多种动态集合操做,它能够被用来表示有序集合,创建索引或优先队列等。所以,在信息学竞赛中,二叉排序树应用很是普遍。c++
做用于二叉排序树上的基本操做,其时间复杂度均与树的高度成正比,对于一棵有 \(n\) 个节点的二叉树,这些操做在最有状况下运行时间为 \(O( \log_2 n)\)。算法
可是,若是二叉树退化成了一条 \(n\) 个节点组成的线性链表,则这些操做在最坏状况下的运行时间为 \(O(n)\)。编程
有些二叉排序树的变形,其基本操做的性能在最坏状况下依然很好,如平衡树(AVL)等。可是,它们须要额外的空间来存储平衡信息,且实现起来比较复杂。同时,若是访问模式不均匀,平衡树的效率就会受到影响,而伸展树却能够克服这些问题。数据结构
伸展树(Splay Tree),是对二叉排序树的一种改进。虽然它并不能保证树一直是“平衡”的,但对于它的一系列操做,能够证实其每一步操做的“平摊时间”复杂度都是 \(O(\log_2 n)\) 。平摊时间是指在一系列最坏状况的操做序列中单次操做的平均时间。因此,从某种意义上来讲,伸展树也是一种平衡的二叉排序树。而在各类树形数据结构中,伸展树的空间复杂度(不须要记录用于平衡的冗余信息)和编程复杂度也都是很优秀的。性能
得到较好平摊效率的一种方法就是使用“自调整”的数据结构,与平衡结构或有明确限制的数据结构相比,自调整的数据结构有一下几个优势:spa
固然,自调整的数据结构也有其潜在的缺点:3d
伸展树是对二叉排序树的一种改进。与二叉排序树同样,伸展树也具备有序性,即伸展树中的每个节点 \(x\) 都知足:该节点左子树中的每个元素都小于 \(x\),而其右子树中的每个元素都大于 \(x\)。code
可是,与普通二叉排序树不一样的是,伸展树能够“自我调整”,这就要依靠伸展树的核心操做 —— \(\text{Splay(x, S)}\)。blog
伸展操做 \(\text{Splay(x, S)}\) 是在保持伸展树有序的前提下,经过一系列旋转,将伸展树 \(\text{S}\) 中的元素 \(\text{x}\) 调整至数的根部。在调整的过程当中,要分如下三种状况分别处理。排序
此时,
通过旋转,使 \(\text{x}\) 成为二叉排序树 \(S\) 的根节点,且依然知足二叉排序树的性质。
\(\text{Zig}\) 操做和 \(\text{Zag}\) 操做如图所示:
此时,咱们设 \(\text{z}\) 为 \(\text{y}\) 的父节点,
如图所示:
此时,咱们设 \(\text{z}\) 为 \(\text{y}\) 的父节点,
下面举一个例子来体会上面的伸展操做。
以下图所示,最左边的一个单链先执行 \(\text{Splay(1, S)}\),咱们将元素 \(1\) 调整到了伸展树的根部。
执行几回 \(\text{Splay(1, S)}\) 的效果
而后再执行 \(\text{Splay(2, S)}\),将元素 \(2\) 调整到伸展树 \(\text{S}\) 的根部。以下图所示:
执行几回 \(\text{Splay(2, S)}\) 的效果
利用伸展树 Splay ,咱们能够在伸展树 \(S\) 上进行以下几种基本操做。
首先,与在二叉排序树中进行查找操做操做同样,在伸展树中查找元素 \(\text{x}\)。若是 \(\text{x}\) 在树中,则再执行 \(\text{Splay(x, S)}\) 调整伸展树。
首先,与在二叉排序树中进行插入操做同样,将 \(\text{x}\) 插入到伸展树 \(\text{S}\) 中的相应位置,再执行 \(\text{Splay(x, S)}\) 调整伸展树。
首先,找到伸展树 \(S1\) 中最大的一个元素 \(\text{x}\),再经过 \(\text{Splay(x, S1)}\) 将 \(\text{x}\) 调整到伸展树 \(S1\) 的根部。而后将 \(S2\) 做为 \(\text{x}\) 节点的右子树插入,这样就获得了新的伸展树 \(S\),如图所示:
\(\text{Join(S1, S2)}\) 的两个步骤
首先,执行 \(\text{Find(x, S)}\) 将 \(\text{x}\) 调整为根节点,而后再对左右子树执行 \(\text{Join(S1, S2)}\) 操做便可。
首先,执行 \(\text{Find(x, S)}\) 将 \(\text{x}\) 调整为根节点,则 \(\text{x}\) 的左子树就是 \(\text{S1}\),右子树就是 \(\text{S2}\)。如图所示:
除了上述介绍的 \(5\) 种基本操做外,伸展树还支持求最大值、最小值、求前趋、求后继等多种操做,这些操做也都是创建在伸展树操做 \(\text{Splay}\) 的基础之上的。
注:这里的代码并非最简单的代码,而是基于上述思想实现的代码,更方便咱们结合以前分析的内容来理解。
下面给出伸展树的各类操做的算法实现,它们都是基于以下伸展树的类型定义:
int lson[maxn], // 左儿子编号 rson[maxn], // 右儿子编号 p[maxn], // 父节点编号 val[maxn], // 节点权值 sz; // 编号范围 [1, sz] struct Splay { int rt; // 根节点编号 void zag(int x); // 左旋 void zig(int x); // 右旋 void splay(int x); // 伸展操做:将x移到根节点 int func_find(int v); // 查找是否存在值为v的节点 void func_insert(int v); // 插入 void func_delete(int v); // 删除 int get_max(); // 求最大值 int get_min(); // 求最小值 int get_pre(int v); // 求前趋 int get_suc(int v); // 求后继 int join(int rt1, int rt2); // 合并 } tree;
void Splay::zag(int x) { int y = p[x], z = p[y], a = lson[x]; lson[x] = y; p[y] = x; rson[y] = a; p[a] = y; p[x] = z; if (z) { if (lson[z] == y) lson[z] = x; else rson[z] = x; } }
Zag(x)操做
void Splay::zig(int x) { int y = p[x], z = p[y], a = rson[x]; rson[x] = y; p[y] = x; lson[y] = a; p[a] = y; p[x] = z; if (z) { if (lson[z] == y) lson[z] = x; else rson[z] = x; } }
Zig(x)操做
void Splay::splay(int x) { while (p[x]) { int y = p[x], z = p[y]; if (!z) { if (x == lson[y]) zig(x); else zag(x); } else if (lson[y] == x) { if (lson[z] == y) { // zig-zig zig(y); zig(x); } else { // zig-zag zig(x); zag(x); } } else { // rson[y] == x if (lson[z] == y) { // zag-zig zag(x); zig(x); } else { // zag-zag zag(y); zag(x); } } } rt = x; }
int Splay::func_find(int v) { int x = rt; while (x) { if (val[x] == v) { rt = x; splay(x); return x; } else if (v < val[x]) x = lson[x]; else x = rson[x]; } return 0; // 返回0说明没找到 }
void Splay::func_insert(int v) { val[++sz] = v; if (rt == 0) { rt = sz; return; } int x = rt; while (true) { if (v < val[x]) { if (lson[x]) x = lson[x]; else { lson[x] = sz; p[sz] = x; break; } } else { if (rson[x]) x = rson[x]; else { rson[x] = sz; p[sz] = x; break; } } } splay(rt = sz); }
void Splay::func_delete(int v) { int x = func_find(v); if (!x) return; int ls = lson[x], rs = rson[x]; lson[x] = rson[x] = 0; p[ls] = p[rs] = 0; rt = join(ls, rs); }
int Splay::get_max() { if (!rt) return 0; int x = rt; while (rson[x]) x = rson[x]; splay(rt = x); return x; }
int Splay::get_min() { if (!rt) return 0; int x = rt; while (lson[x]) x = lson[x]; splay(rt = x); return x; }
int Splay::get_pre(int v) { if (!rt) return 0; int x = rt, ans = 0; while (true) { if (val[x] <= v) { if (!ans || val[ans] < val[x]) ans = x; if (rson[x]) x = rson[x]; else break; } else { if (lson[x]) x = lson[x]; else break; } } if (ans) splay(rt = ans); return ans; }
int Splay::get_suc(int v) { if (!rt) return 0; int x = rt, ans = 0; while (true) { if (val[x] >= v) { if (!ans || val[ans] > val[x]) ans = x; if (lson[x]) x = lson[x]; else break; } else { if (rson[x]) x = rson[x]; else break; } } if (ans) splay(rt = ans); return ans; }
int Splay::join(int rt1, int rt2) { if (!rt1) return rt2; if (!rt2) return rt1; Splay tree1; tree1.rt = rt1; rt1 = tree1.get_max(); assert(rson[rt1] == 0); rson[rt1] = rt2; p[rt2] = rt1; return rt1; }
示例代码(对应题目:《怪物仓库管理员(二)》):
#include <bits/stdc++.h> using namespace std; const int maxn = 500050; int lson[maxn], // 左儿子编号 rson[maxn], // 右儿子编号 p[maxn], // 父节点编号 val[maxn], // 节点权值 sz; // 编号范围 [1, sz] struct Splay { int rt; // 根节点编号 void zag(int x); // 左旋 void zig(int x); // 右旋 void splay(int x); // 伸展操做:将x移到根节点 int func_find(int v); // 查找是否存在值为v的节点 void func_insert(int v); // 插入 void func_delete(int v); // 删除 int get_max(); // 求最大值 int get_min(); // 求最小值 int get_pre(int v); // 求前趋 int get_suc(int v); // 求后继 int join(int rt1, int rt2); // 合并 } tree; /** zag(int x) 左旋 */ void Splay::zag(int x) { int y = p[x], z = p[y], a = lson[x]; lson[x] = y; p[y] = x; rson[y] = a; p[a] = y; p[x] = z; if (z) { if (lson[z] == y) lson[z] = x; else rson[z] = x; } } /** zig(int x) 右旋 */ void Splay::zig(int x) { int y = p[x], z = p[y], a = rson[x]; rson[x] = y; p[y] = x; lson[y] = a; p[a] = y; p[x] = z; if (z) { if (lson[z] == y) lson[z] = x; else rson[z] = x; } } /** splay(int x) 伸展操做 */ void Splay::splay(int x) { while (p[x]) { int y = p[x], z = p[y]; if (!z) { if (x == lson[y]) zig(x); else zag(x); } else if (lson[y] == x) { if (lson[z] == y) { // zig-zig zig(y); zig(x); } else { // zig-zag zig(x); zag(x); } } else { // rson[y] == x if (lson[z] == y) { // zag-zig zag(x); zig(x); } else { // zag-zag zag(y); zag(x); } } } rt = x; } int Splay::func_find(int v) { int x = rt; while (x) { if (val[x] == v) { rt = x; splay(x); return x; } else if (v < val[x]) x = lson[x]; else x = rson[x]; } return 0; // 返回0说明没找到 } void Splay::func_insert(int v) { val[++sz] = v; if (rt == 0) { rt = sz; return; } int x = rt; while (true) { if (v < val[x]) { if (lson[x]) x = lson[x]; else { lson[x] = sz; p[sz] = x; break; } } else { if (rson[x]) x = rson[x]; else { rson[x] = sz; p[sz] = x; break; } } } splay(rt = sz); } void Splay::func_delete(int v) { int x = func_find(v); if (!x) return; int ls = lson[x], rs = rson[x]; lson[x] = rson[x] = 0; p[ls] = p[rs] = 0; rt = join(ls, rs); } int Splay::get_max() { if (!rt) return 0; int x = rt; while (rson[x]) x = rson[x]; splay(rt = x); return x; } int Splay::get_min() { if (!rt) return 0; int x = rt; while (lson[x]) x = lson[x]; splay(rt = x); return x; } int Splay::get_pre(int v) { if (!rt) return 0; int x = rt, ans = 0; while (true) { if (val[x] <= v) { if (!ans || val[ans] < val[x]) ans = x; if (rson[x]) x = rson[x]; else break; } else { if (lson[x]) x = lson[x]; else break; } } if (ans) splay(rt = ans); return ans; } int Splay::get_suc(int v) { if (!rt) return 0; int x = rt, ans = 0; while (true) { if (val[x] >= v) { if (!ans || val[ans] > val[x]) ans = x; if (lson[x]) x = lson[x]; else break; } else { if (rson[x]) x = rson[x]; else break; } } if (ans) splay(rt = ans); return ans; } int Splay::join(int rt1, int rt2) { if (!rt1) return rt2; if (!rt2) return rt1; Splay tree1; tree1.rt = rt1; rt1 = tree1.get_max(); assert(rson[rt1] == 0); rson[rt1] = rt2; p[rt2] = rt1; return rt1; } int n, op, x; int main() { cin >> n; while (n --) { cin >> op; if (op != 3 && op != 4) cin >> x; if (op == 1) tree.func_insert(x); else if (op == 2) tree.func_delete(x); else if (op == 3) cout << val[tree.get_min()] << endl; else if (op == 4) cout << val[tree.get_max()] << endl; else if (op == 5) cout << val[tree.get_pre(x)] << endl; else cout << val[tree.get_suc(x)] << endl; } return 0; }