學時總結 學時 VI SPLAY伸展樹

2022-04-30 23:27:11 字數 4268 閱讀 7563

平衡樹之多,學之不盡也……

二叉排序樹的一種,自動平衡,由 tarjan 提出並實現。得名於特有的 splay 操作。

splay操作:將節點u通過單旋、雙旋移動到某乙個指定位置。

主要目的是將訪問頻率高的節點在不改變原順序的前提下移動到盡量靠近根節點的位置,以此來解決同乙個(相似)問題的多次查詢。

但是在非降序查詢每乙個節點後,splay 樹會變為一條鏈,降低運算效率。 

二叉排序樹必須滿足 左兒子《根節點《右兒子 ,即使在旋轉過後也是如此。因此旋轉操作(rotate)是splay平衡樹的乙個重要組成部分。而在splay操作中,旋轉分單旋和雙旋。

單旋:

由於rotate分成兩種情況,許多oier直接把兩種情況分類討論寫在程式裡,這樣就使得rotate()函式及其之長。但是老師教了我們乙個不錯的儉省**的方法(~^o^~):

首先我們定義x的左兒子為 tree[x].ch[0],右兒子為 ch[1],再在rotate()函式的參數列中加上"d",d=1表示右旋,0表示左旋。

void rotate(node *x,int

d)

完美地契合了上圖的規律,從而達到簡短**的目的!

雙旋:

不用怎麼解釋……其實就是3個點(兒子x,父親y,祖父z)之間將兒子x轉移到祖父z位置的2次旋轉操作。第一次旋轉能夠將兒子x旋轉到父親y位置,此時的旋轉和祖父z沒有關係,就看成x,y的旋轉;第一次旋轉後,y就成了x的一棵子樹,所以第二次旋轉是z和x之間的……總而言之就是兩次單旋,只是注意旋轉方向,保證原有關係不變。

舉個例子:

reader 們可以把剩下的3種自己試一試,有什麼不懂的可以在文末的郵箱處ask我 (^.^)~

實質是旋轉的組合……

作為splay樹的核心,它能夠實現將指定節點旋轉到某乙個位置(或某乙個節點的兒子的位置)的操作。通過splay操作,我們可以每一次將查詢的節點向高處提,從而下一次訪問該節點時速度加快。

設當前需要轉移的節點為x,節點y,z分別是它的父親,祖父,x需要轉移到節點rt的下方。由於每一次rotate操作每一次可以使節點上移一層(目的一般不會是下移),如果z就是rt,就說明y是x要到達的地方(因為z的下面就是y),而x到y只需要一次rotate,因此呼叫單旋。

其他情況下至少需要兩次rotate操作,即雙旋。直到到達目標位置為止。

如何判斷是左旋還是右旋?

我們很容易發現乙個規律——如果要使v上移到u(u是v的父親),當v是u的左兒子時,我們需要右旋,而v是u的右兒子時,需要左旋……也就是說兒子的左右和旋轉方向的左右是恰好相反的。

這是所有操作中最簡單的乙個,只用到了二叉排序樹的性質。

設查詢點的值為val,從根節點開始查詢,設當前查詢到的點值為u。由於根的左子樹小於根,而右子樹大於根,所以u>val時向左子樹查詢,否則向右子樹查詢,直到查詢到值或者當前節點為空null。

基本思想和查詢節點很像,也是根據二叉排序樹來確定位置。

當我們找到乙個值恰好為特定值的節點,則將該節點的個數+1,不再插入節點了。與查詢不同的是它如果按順序查詢節點,發現該節點為null,就說明沒有值為val的節點,此時我們會新建乙個值為val的節點插入到那個為null的節點。

這裡的排名不包括並列(2,3,3,4 3的排名為2或3,4的排名為4)。其實就是比他小(嚴格小於)的元素個數+1,而比他小的元素恰好就是他的左子樹,因此也就是它的左子樹的個數+1。

查詢特定排名的點要麻煩一些……設當前節點為u,當u的左子樹+1大於排名,則說明當前數過大,向左子樹查詢,否則向右子樹查詢。若查詢右子樹,則先將特定排名減去當前節點的左子樹大小,表示在右子樹中需要找到第"特定排名減去當前節點的左子樹大小"大的元素。

換句話說,當前節點為u,向u的右子樹查詢,則目標節點在u的右子樹中的排名為 (以u為根的子樹中的排名 - u的左子樹大小)。

還是先像查詢特定值的節點的思路,先找到要刪除的節點的位置。由於我把值相同的點壓縮在了乙個點上,值相同的點的個數為cnt。當cnt>1時,即不止乙個點值為特定值,我們可以直接cnt--;如果cnt=1,則刪除該點後,該點就沒了……這時候我們需要處理節點與其前驅後繼的關係。我們可以把前驅通過splay移動到根節點,而把後繼移到前驅的右兒子。我們會發現後繼的左兒子就是要刪除的節點,且它沒有兒子(葉結點),所以我們直接把左兒子改為null,再update更新節點個數,好像就完了(=^-ω-^=)

(ps.下面這個模板實現了插入insert,刪除delete(無法判斷是否存在該元素),查詢節點getkey,正反向查詢排名find_count/get_num,查詢前驅後繼frontbehind)

1

/*lucky_glass

*/2 #include3 #include4 #include5

using

namespace

std;

6struct

node

13int cmp(int x)const

17 }*root; //

樹根18

int get_size(node *p) //

避免點為null時訪問size錯誤

1922

void update(node *x) //

上傳子樹大小

2326

void rotate(node *x,int d) //

旋轉,d=0左旋,d=1右旋

2736

void splay(node *x,node *rt)

3756

update(x);57}

58void insert(int val) //

插入值為val的節點

5961

//插入節點

62 node *y=root;

63while(true)64

66//

如果已經存在值為val的節點,則該節點個數+1

67 node *&ch=(valv? y->ch[0]:y->ch[1

]);68

if(ch==null) break

;69 y=ch;70}

71 node *x=new

node(val);

72 (valv? y->ch[0]:y->ch[1])=x;

73 x->fa=y;

74splay(x,null);75}

76 node *find(node *x,int d) //

尋找前驅後繼(d=0前驅,d=1後繼),只能尋找已存在於樹中的值

7781

void delete(int num) //

刪除乙個值為num的節點

82104

}105

else p->cnt--; //

減少個數

106return

;107

}108 p=p->v>num? p->ch[0]:p->ch[1

];109

}110

}111 node *getkey(node *o,int x) //

根據二叉排序樹關係查詢值為x的節點

112117

int find_count(int val) //

找到值為val的節點在樹上的排名

118123

int get_num(int num) //

找到排名為num的數

124136

}137

splay(now,null);

138return now->v;

139}

140int frontbehind(int num,int d) //

找前驅後繼(不一定在樹上)

141147

intmain()

148162

}163

return0;

164 }

這個**風格可能比較奇怪,因為是從幾個不同的**裁剪修改,然後組合起來的……(∩╹□╹∩)

- lucky_glass

(tab:如果我有沒講清楚的地方可以直接在郵箱[email protected] email我,在週末我會盡量解答並完善部落格~)

大學時候讀的書

著作權歸作者所有。讀書可以先讀史。讀各個學科的簡史。大致了解知識的廣度,然後再挑自己感興趣的來讀。大概各個學科本科生差不多水平該讀的書。下面是我都看過的覺得適合入門的書。西方哲學史 豆瓣 中國哲學簡史 豆瓣 中庸 豆瓣 金枝 豆瓣 什麼是數學 豆瓣 微觀經濟學 豆瓣 通往奴役之路 豆瓣 心理學 豆瓣...

考研數學時間分配

1.準確掌握答題時間 考試時長是3小時,答題的時間分配一般可以按照如下方式 選擇題和填空題約1小時,解答題約1小時40分鐘,預留20分鐘檢查和補做前面未做的題,以及作為機動和迴旋餘地。選擇題和填空題每題一般花4 5分鐘,如果一道題3分鐘仍無思路則應跳過。解答題每題一般花11分鐘左右,一道題如果4 5...

中學時候的「黑客筆記」

晚上清理硬碟,無意中發現了很多年前我留下的 黑客學習筆記 和 黑客工具 我在中學時候,曾極度痴迷於所謂的 黑客技術 尤其是喜愛鑽研windows平台上的漏洞,入侵等。我有五六年不倒騰那些玩意了,現在對於那些命令和工具,似乎已經感到很陌生了。但是對於基本的原理和入侵思想,還是有點理解的。其實我當初應該...