二叉排序樹可以支持多種動態集合操做,它能夠被用來表示有序集合,創建索引或優先隊列等。所以,在信息學競賽中,二叉排序樹應用很是普遍。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; }