splay詳解(一)

前言

Spaly是基於二叉查找樹實現的,數據結構

什麼是二叉查找樹呢?就是一棵樹唄:joy: ,可是這棵樹知足性質—一個節點的左孩子必定比它小,右孩子必定比它大ide

好比說函數

這就是一棵最基本二叉查找樹spa

對於每次插入,它的指望複雜度大約是$logn$級別的,可是存在極端狀況,好比9999999 9999998 9999997.....1這種數據,會直接被卡成$n^2$code

在這種狀況下,平衡樹出現了!blog

Splay簡介

Splay是平衡樹的一種,中文名爲伸展樹,由丹尼爾·斯立特Daniel Sleator和羅伯特·恩卓·塔揚Robert Endre Tarjan在1985年發明的(mmp怎麼又是tarjan)排序

它的主要思想是:對於查找頻率較高的節點,使其處於離根節點相對較近的節點io

這樣就能夠保證了查找的效率class

那麼如今問題來了:效率

  • 什麼樣的點是查找頻率高的點?

這個玩意兒確實很差統計,可是你能夠認爲每次被查找的點查找頻率相對較高,說白了就是你把每次查找到的點搬到根節點去

固然你也能夠每次查找以後隨機一個點做爲根,因而Treaplay這種數據結構就誕生啦

  •  怎麼實現把節點搬到根這種操做?

這也是Splay這種數據結構所要實現的功能,接下來咱們詳細的介紹一下

Splay基本操做

rotate

首先考慮一下,咱們要把一個點挪到根,那咱們首先要知道怎麼讓一個點挪到它的父節點

狀況1

當X是Y的左孩子

 

這時候若是咱們讓X成爲Y的父親,只會影響到3個點的關係

B與X,X與Y,X與R

根據二叉排序樹的性質

B會成爲Y的左兒子

Y會成爲X的右兒子

X會成爲R的兒子,具體是什麼兒子,這個要看Y是R的啥兒子

通過變換以後,大概是這樣

狀況2

當X是Y的右孩子

本質上和上面是同樣的,

變換後爲

 

這兩種代碼單獨實現都比較簡單,我就不寫了(其實是我懶)

可是這兩種旋轉狀況很相似,第二種狀況實際就是把第一種狀況的X,Y換了換位置

咱們考慮一下能不能將這兩種狀況合併起來實現呢?

答案是確定的

首先咱們要獲取到每個節點它是它爸爸的哪一個孩子,能夠這麼寫

 

bool ident(int x) {
    return tree[tree[x].fa].ch[0] == x ? 0 : 1;
}

 

 

 

若是是左孩子的話會返回0,右孩子會返回1

那麼咱們不可貴到R,Y,X這三個節點的信息

 

int Y = tree[x].fa;
int R = tree[Y].fa;
int Yson = ident(x); //x是y的哪一個孩子
int Rson = ident(Y);

 

B的狀況咱們能夠根據X的狀況推算出來,根據^運算的性質,0^1=1,1^1=0,2^1=3,3^1=2,並且B相對於X的位置必定是與X相對於Y的位置是相反的

(不然在旋轉的過程當中不會對B產生影響)

 

int B = tree[x].ch[Yson ^ 1];

 

而後咱們考慮鏈接的過程

根據上面的圖,不可貴到

B成爲Y的哪一個兒子與X是Y的哪一個兒子是同樣的

Y成爲X的哪一個兒子與X是Y的哪一個兒子相反

X成爲R的哪一個兒子與Y是R的哪一個兒子相同

 

connect(B, Y, Yson);
connect(Y, x, Yson ^ 1);
connect(x, R, Rson);

connect函數這麼寫,挺顯然的

void connect(int x, int fa, int how) { //x節點將成爲fa節點的how孩子
    tree[x].fa = fa;
    tree[fa].ch[how] = x;
}

 

單旋函數就是這樣了,利用這個函數就能夠實現把一個節點搬到它的爸爸那兒了,

Splay

Splay(x,to)是實現把x節點搬到to節點

最簡單的辦法,對於x這個節點,每次上旋直到to

可是!

若是你真的這麼寫,可能會T成SB,出題人可能會構造數據把單旋卡成$n^2$,不要問我爲何!(實際上是我不知道)

一個感性的理解是這樣的

把一個點雙旋到根,可使得從根到它的路徑上的全部點的深度變爲大約原來的一半,其它點的深度最多增長2

或者你能夠了解一下爲啥單旋是錯的

下面咱們介紹一下雙旋的Splay

這裏的狀況有不少,可是總的來講就三種狀況

1.to是x的爸爸,

這樣的話吧x旋轉上去就好

update in 2018.2.19

這裏可能寫錯了一個地方(其實也沒有寫錯)

由於咱們在雙旋的時候會改變三個點的關係,爲了方別寫,因此咱們開始的時候把to設置爲to的爸爸

if (tree[tree[x].fa].fa == to) rotate(x);

2.x和他爸爸和他爸爸的爸爸在一條線上

這時候先把Y旋轉上去,再把X旋轉上去就好

else if (ident(x) == ident(tree[x].fa)) rotate(tree[x].fa), rotate(x);

 

3.x和他爸爸和他爸爸的爸爸不在一條線上

這時候把X旋轉兩次就好

總的代碼:

 

void splay(int x, int to) {
    to = tree[to].fa;
    while (tree[x].fa != to) {
        if (tree[tree[x].fa].fa == to) rotate(x);
        else if (ident(x) == ident(tree[x].fa)) rotate(tree[x].fa), rotate(x);
        else rotate(x), rotate(x);
    }
}

 

 

 

後記

至此,Spaly的最核心最基本的操做已經講解完畢

至於這玩意兒怎麼用,以及能實現什麼功能,且聽下回分解

相關文章
相關標籤/搜索