伸展樹

伸展樹（Splay Tree）是一種能夠自我平衡的二叉查詢樹，它能在均攤的時間內完成基於伸展（Splay）操作的插入、查詢、修改和刪除操作。它是由丹尼爾・斯萊託和羅伯特・塔揚在 1985 年發明的。

在伸展樹上的一般操作都基於伸展操作：假設想要對一個二叉查詢樹執行一系列的查詢操作，為了使整個查詢時間更小，被查頻率高的那些條目就應當經常處於靠近樹根的位置。於是想到設計一個簡單方法，在每次查詢之後對樹進行調整，把被查詢的條目搬移到離樹根近一些的地方。伸展樹應運而生。伸展樹是一種自調整形式的二叉查詢樹，它會沿著從某個節點到樹根之間的路徑，透過一系列的旋轉把這個節點搬移到樹根去。

它的優勢在於不需要記錄用於平衡樹的冗餘資訊。

優點

伸展樹的自我平衡使其擁有良好的效能，因為頻繁訪問的節點會被移動到更靠近根節點，進而獲得更快的訪問速度。

可靠的效能 —— 它的平均效率不輸於其他平衡樹。
儲存所需的記憶體少 —— 伸展樹無需記錄額外的什麼值來維護樹的資訊，相對於其他平衡樹，記憶體佔用要小。

缺點

伸展樹最顯著的缺點是它有可能會變成一條鏈。例如，在以非遞減順序訪問全部 n 個之後就會出現這種情況。此時樹的高度對應於最壞情況的時間效率，操作的實際時間效率可能很低。然而均攤的最壞情況是對數級的 ——。

即使以 “只讀” 方式（例如透過查詢操作）訪問伸展樹，其結構也可能會發生變化。這使得伸展樹在多執行緒環境下會變得很複雜。具體而言，如果允許多個執行緒同時執行查詢操作，則需要額外的維護和操作。這也使得它們不適合在純粹的函數語言程式設計中普遍使用，儘管用於實現優先順序佇列的方式不多。

操作

3.1 伸展 (splay)

當一個節點 x 被訪問過後，伸展操作會將 x 移動到根節點。為了進行伸展操作，我們會進行一系列的旋轉，每次旋轉會使 x 離根節點更近。透過每次訪問節點後的伸展操作，最近訪問的節點都會離根節點更近，且伸展樹也會大致平衡，這樣我們就可以得到期望均攤時間複雜度的下界 —— 均攤。

每次旋轉操作由三個因素決定：

x 是其父節點 p 的左兒子還是右兒子；
p 是否為根；
p 是其父節點 g（x 的祖父節點）的左兒子還是右兒子。

在每次旋轉操作後，設定 p 的兒子為 x 是很重要的。如果 p 為空，那麼 x 顯然就是根節點了。

共有三種旋轉操作，每種都有右旋（Zig）和左旋（Zag）兩種情況。為了簡單起見，對每種旋轉操作只展示一種情況。這些旋轉操作是：

Zig：當 p 為根節點時進行。Zig 通常只在伸展操作的最後一步進行。

Zig-zig 和 Zag-zag：當 p 不為根節點且 x 和 p 都為左兒子或都為右兒子時進行。下圖為 x 和 p 都為左兒子時的情況（即 Zig-zig），需先將 p 右旋到 g 的位置，再將 x 右旋到 p 的位置。

center

Zig-zag 和 Zag-zig：當 p 不為根節點且 x 為左兒子而 p 為右兒子時進行，反之亦然。下圖為前述情況（即 Zig-zag），需先將 x 左旋到 p 到的位置，再將 x 右旋到 g 的位置。

center

3.2 連線 (join)

給出兩棵樹 S 和 T，且 S 的所有元素都比 T 的元素要小。下面的步驟可以把它們連線成一棵樹：

伸展 S 中最大的節點。現在這個節點變為 S 的根節點，且沒有右兒子。
令 T 的根節點變為其右兒子。

3.3 分割 (split)

給出一棵樹和一個元素 x，返回兩棵樹：一棵中所有的元素均小於等於 x，另一棵中所有的元素大於 x。下面的步驟可以完成這個操作：

伸展 x。這樣的話 x 成為了這棵樹的根所以它的左子樹包含了所有比 x 小的元素，右子樹包含了所有比 x 大的元素。
把 x 的右子樹從樹中分割出來。

3.4 插入 (insert)

插入操作是一個比較複雜的過程，具體步驟如下:

我們假定要插入的值為 k。

如果當前樹為空，則直接插入根。

如果當前節點的權值等於 k 則增加當前節點的大小並更新節點和父親的資訊，將當前節點進行 splay 操作。

否則按照二叉查詢樹的性質向下找，找到空節點就插入即可，當然在最後還要進行一次 splay 操作。

3.5 刪除 (delete)

令要刪除的節點為 x，對 x 進行一次 splay 操作將其移動到根節點的位置。

若 x 的大小大於 1，則將 x 的大小減一然後結束刪除操作。

否則將 x 刪除然後執行 join 操作合併 x 的左右子樹並重新指定根。

3.6 查詢 (find)

如同一般的查詢樹的查詢方式。

實現

以下是伸展樹的 C++ 實現（用指標實現）

include
ifndef SPLAY_TREE
define SPLAY_TREE

template >

class splay_tree {

private:

Comp comp;

unsigned long p_size;

struct node {

node left, right;

node *parent;

T key;

node (const T& init = T ) : left (0), right (0), parent (0), key (init) } *root;

void left_rotate (node x) {node y = x->right;

x->right = y->left;

if (y->left ) y->left->parent = x;

y->parent = x->parent;

if (x->parent ) {if ( x == x->parent->left ) x->parent->left = y;

else x->parent->right = y;

}

y->left = x;

x->parent = y;

}

void right_rotate (node x) {node y = x->left;

x->left = y->right;

if (y->right ) y->right->parent = x;

y->parent = x->parent;

if (x->parent ) {if ( x == x->parent->left ) x->parent->left = y;

else x->parent->right = y;

}

y->right = x;

x->parent = y;

}

void splay (node *x) {while ( x->parent ) {if ( !x->parent->parent ) {if ( x->parent->left == x ) right_rotate (x->parent );

else left_rotate (x->parent );

} else if (x->parent->left == x && x->parent->parent->left == x->parent ) {right_rotate ( x->parent->parent );

right_rotate (x->parent );

} else if (x->parent->right == x && x->parent->parent->right == x->parent ) {left_rotate ( x->parent->parent );

left_rotate (x->parent );

} else if (x->parent->left == x && x->parent->parent->right == x->parent ) {right_rotate ( x->parent );

left_rotate (x->parent );

} else {left_rotate ( x->parent );

right_rotate (x->parent );

}

}

}

void replace (node u, node v) {if ( !u->parent ) root = v;

else if (u == u->parent->left ) u->parent->left = v;

else u->parent->right = v;

if (v) v->parent = u->parent;

}

node subtree_minimum (node u) {while ( u->left ) u = u->left;

return u;

}

node subtree_maximum (node u) {while ( u->right ) u = u->right;

return u;

}

public:

splaytree : root ( 0), psize (0) { }

void insert (const T &key) {

node *z = root;

node *p = 0;

while (z) {

p = z;

if (comp ( z->key, key ) ) z = z->right;

else z = z->left;

}

z = new node (key);

z->parent = p;

if (!p) root = z;

else if (comp ( p->key, z->key ) ) p->right = z;

else p->left = z;

splay (z);

p_size++;

}

node* find (const T &key) {

node *z = root;

while (z) {if ( comp ( z->key, key ) ) z = z->right;

else if (comp ( key, z->key ) ) z = z->left;

else return z;

}

return 0;

}

void erase (const T &key) {node *z = find ( key);

if (!z) return;

splay (z);

if (!z->left ) replace (z, z->right );

else if (!z->right ) replace (z, z->left );

else {node *y = subtree_minimum ( z->right );

if (y->parent != z ) {replace ( y, y->right );

y->right = z->right;

y->right->parent = y;

}

replace (z, y);

y->left = z->left;

y->left->parent = y;

}

p_size--;

}

const T& minimum { return subtree_minimum ( root)->key; }

const T& maximum { return subtree_maximum ( root)->key; }

bool empty const { return root == 0;}

unsigned long size const { return p_size;}

};

endif // SPLAY_TREE

時間效率分析

m 次伸展操作的均攤時間效率

實際時間效率