带旋treap概念及模板,带例题:普通平衡树
帶旋Treap
- 二叉查找樹BST(Binary Search Tree)定義
- Treap定義
- 模板合集(均為O(logn)O(logn)O(logn))
- push_up模板
- 旋轉(zhuǎn)模板
- 插入模板
- 刪除模板
- 查找前驅(qū)模板
- 查找后驅(qū)模板
- 查找鍵值key模板
- 查找節(jié)點的修正值rank模板
- PS:rd的比較問題
- 例題:普通平衡樹
- 題目
- 代碼實現(xiàn)
真的太難了,剛開始是直接用模板不斷A題,其實自己根本不是很理解,于是今天放棄了刷題時間,理解并背誦了這個模板,寫了這篇blog
二叉查找樹BST(Binary Search Tree)定義
二叉查找樹(Binary Search Tree)是基于插入思想的一種在線的排序數(shù)據(jù)結(jié)構(gòu),它又叫二叉搜索樹(Binary Search Tree)、二叉排序樹(Binary Sort Tree),簡稱BST
這種數(shù)據(jù)結(jié)構(gòu)的基本思想是在二叉樹的基礎(chǔ)上,規(guī)定一定的順序,使數(shù)據(jù)可以有序地存儲
二叉查找樹運用了像二分查找一樣的查找方式,并且基于鏈式結(jié)構(gòu)存儲,從而實現(xiàn)了高效的查找效率和完美的插入時間
二叉查找樹(Binary Search Tree)或者是一棵空樹,或者是具有下列性質(zhì)的二叉
樹:
我只是為了讓大家理解下面Treap的定義罷了
Treap定義
Treap是一種平衡樹。這個單詞的構(gòu)造選取了Tree(樹)的前兩個字符和Heap(堆)的后三個字符,
Treap = Tree + Heap。
顧名思義,Treap把BST和Heap結(jié)合了起來。
它和BST一樣滿足許多優(yōu)美的性質(zhì),而引入堆目的就是為了維護平衡。
Treap在BST的基礎(chǔ)上,添加了一個修正值。在滿足BST性質(zhì)的基礎(chǔ)上,Treap節(jié)點的修正值還滿足最小堆性質(zhì) 。最小堆性質(zhì)可以被描述為每個子樹根節(jié)點都小于等于其子節(jié)點。
Treap的定義
Treap可以定義為有以下性質(zhì)的二叉樹:
的根節(jié)點的修正值小于等于左子樹根節(jié)點的修正值;
的根節(jié)點的修正值小于等于右子樹根節(jié)點的修正值;
修正值是節(jié)點在插入到Treap中時隨機生成的一個值,它與節(jié)點的值無關(guān)。
Treap維護平衡的原理
我們發(fā)現(xiàn),BST會遇到不平衡的原因是因為有序的數(shù)據(jù)會使查找的路徑退化成鏈,
而隨機的數(shù)據(jù)使BST退化的概率是非常小的
在Treap中,修正值的引入恰恰是使樹的結(jié)構(gòu)不僅僅取決于節(jié)點的值,還取決于修正值的值然而修正值的值是隨機生成的,出現(xiàn)有序的隨機序列是小概率事件,所以Treap的結(jié)構(gòu)是趨向于隨機平衡的
模板合集(均為O(logn)O(logn)O(logn))
先給明要用到的數(shù)組含義:
Size:表示節(jié)點數(shù)量也可作最后一個點編號
cnt[p]:表示編號為p,值為x在treap中插入的次數(shù)
key[p]:表示該點p的值為x
rd[p]:就是我們自己搞的修正值,用rand函數(shù)隨機生成
siz[p]:編號為p的子樹包括本身在內(nèi)的節(jié)點數(shù)量即大小
son[p][2]:son[p][0]表示p的左兒子,son[p][1]表示p的右兒子
push_up模板
就是更新x的子樹大小,一定要多次更新,因為后面的操作總是要用到
一句話:不用白不用,能多用就多用
旋轉(zhuǎn)模板
為了使Treap中的節(jié)點同時滿足BST性質(zhì)和小根堆性質(zhì),不可避免地要對其結(jié)構(gòu)進行調(diào)整,調(diào)整方式被稱為旋轉(zhuǎn)(Rotate)
在維護Treap的過程中,只有兩種旋轉(zhuǎn) ,分別是左旋和右旋。旋轉(zhuǎn)是相對于子樹而言的,左旋和右旋的命名體現(xiàn)了旋轉(zhuǎn)的一條性質(zhì)1:
左旋一個子樹,會把它的根節(jié)點旋轉(zhuǎn)到根的左子樹位置,同時根節(jié)點的右子節(jié)點成為子樹的根;
右旋一個子樹,會把它的根節(jié)點旋轉(zhuǎn)到根的右子樹位置,同時根節(jié)點的左子節(jié)點成為子樹的根
如圖:
性質(zhì)2:旋轉(zhuǎn)后的子樹仍然滿足BST性質(zhì)
void rotate ( int &x, int d ) {//0左旋,1右旋 int p = son[x][!d];son[x][!d] = son[p][d];son[p][d] = x;push_up ( x );push_up ( p );x = p; }
插入模板
在Treap中插入元素,與在BST中插入方法相似。首先找到合適的插入位置,然后建立新的節(jié)點,存儲元素。但是要注意建立新的節(jié)點的過程中,會隨機地生成一個修正值,這個值可能會破壞堆序,因此我們要根據(jù)需要進行恰當?shù)男D(zhuǎn)。具體方法如下:
如果左子節(jié)點的修正值小于當前節(jié)點的修正值,對當前節(jié)點進行右旋;
右子節(jié)點的修正值小于當前節(jié)點的修正值,對當前節(jié)點進行左旋;
子樹為空,插入成功
刪除模板
Treap的刪除因為要維護堆序,比較好的方法是利用旋轉(zhuǎn)的方法把要刪除的結(jié)點旋轉(zhuǎn)到葉結(jié)點位置,再做刪除操作。
情況一,該節(jié)點為葉節(jié)點,則該節(jié)點是可以直接刪除的節(jié)點。若該節(jié)點有非空子節(jié)點,用非空子節(jié)點代替該節(jié)點的,否則用空節(jié)點代替該節(jié)點,然后刪除該節(jié)點。
情況二,該節(jié)點有兩個非空子節(jié)點。我們的策略是通過旋轉(zhuǎn),使該節(jié)點變?yōu)榭梢灾苯觿h除的節(jié)點。如果該節(jié)點的左子節(jié)點的修正值小于右子節(jié)點的修正值,右旋該節(jié)點,使該節(jié)點降為右子樹的根節(jié)點,然后訪問右子樹的根節(jié)點,繼續(xù)討論;反之,左旋該節(jié)點,使該節(jié)點降為左子樹的根節(jié)點,然后訪問左子樹的根節(jié)點,繼續(xù)討論,直到變成可以直接刪除的節(jié)點
例如:要刪除節(jié)點6,找到節(jié)點6,發(fā)現(xiàn)6有兩個兒子,但是右兒子的修正值小于左兒子,選擇左旋
接著發(fā)現(xiàn)6只剩下一個兒子了,就直接右旋讓左兒子代替它的位置隨后刪掉
接下來就比較輕松了,好理解了
查找前驅(qū)模板
找前驅(qū)我們發(fā)現(xiàn),其實就是一直往左兒子找,如果節(jié)點p與查找值x大小比較后要查找p的右兒子,這個右兒子就是x的一個前驅(qū)但不一定是最接近的,所以需要比較并繼續(xù)往下找,掘地三尺
int pre ( int rt, int x ) {if ( ! rt )return -INF;if ( key[rt] >= x )return pre ( son[rt][0], x );elsereturn max ( key[rt], pre ( son[rt][1], x ) ); }查找后驅(qū)模板
找后驅(qū)我們發(fā)現(xiàn)與前驅(qū)相似,其實就是一直往右兒子找,如果節(jié)點p與查找值x大小比較后要查找p的左兒子,這個左兒子就是x的一個前驅(qū)但不一定是最接近的,所以需要比較
int suf ( int rt, int x ) {if ( ! rt )return INF;if ( key[rt] <= x )return suf ( son[rt][1], x );elsereturn min ( key[rt], suf ( son[rt][0], x ) ); }查找鍵值key模板
就是與siz子樹個數(shù)比較,看屬于哪個部分
int find ( int rt, int x ) {if ( ! rt )return 0;if ( siz[son[rt][0]] >= x )return find ( son[rt][0], x );else if ( siz[son[rt][0]] + cnt[rt] < x )return find ( son[rt][1], x - cnt[rt] - siz[son[rt][0]] );elsereturn key[rt]; }查找節(jié)點的修正值rank模板
和find差不多的思想,應該很好理解的
int search_rank ( int rt, int x ) {if ( ! rt )return 0;if ( key[rt] == x )return siz[son[rt][0]] + 1;if ( key[rt] < x )return siz[son[rt][0]] + cnt[rt] + search_rank ( son[rt][1], x );return search_rank ( son[rt][0], x ); }PS:rd的比較問題
認真的同學會發(fā)現(xiàn),我在寫講解博客的時候是維護的rd從小到大,而代碼卻是寫的從大到小,其實這rd本來就是我們自己強加的修正值,所以本質(zhì)是沒有太大的區(qū)別的,只需要注意對應insert和delet維護好自己鎖定的rd順序就行了
1:
2:
insert:if ( rd[p] > rd[son[p][d]] ) delet:int d = rd[son[p][0]] < rd[son[p][1]];例題:普通平衡樹
題目
點擊查看題目
代碼實現(xiàn)
既然是入門模板題,自然是把所有的模板拼接上再加上輸入輸出即可AC,不再多說!!
#include <cstdio> #include <iostream> #include <algorithm> using namespace std; #define MAXN 100005 #define INF 0x7f7f7f7f int n, Size; int siz[MAXN], cnt[MAXN], key[MAXN], rd[MAXN]; int son[MAXN][2];void push_up ( int x ) {siz[x] = siz[son[x][0]] + siz[son[x][1]] + cnt[x]; }void rotate ( int &x, int d ) {int p = son[x][!d];son[x][!d] = son[p][d];son[p][d] = x;push_up ( x );push_up ( p );x = p; }void insert ( int &rt, int x ) {if ( ! rt ) {rt = ++ Size;siz[rt] = cnt[rt] = 1;key[rt] = x;rd[rt] = rand();return;}if ( key[rt] == x ) {cnt[rt] ++;siz[rt] ++;return;}int d = x > key[rt];insert ( son[rt][d], x );if ( rd[rt] < rd[son[rt][d]] )rotate ( rt, !d );push_up ( rt ); }void delet ( int &rt, int x ) {if ( ! rt )return;if ( x != key[rt] )delet ( son[rt][x > key[rt]], x );else {if ( ! son[rt][0] && ! son[rt][1] ) {cnt[rt] --;siz[rt] --;if ( ! cnt[rt] )rt = 0;}else if ( son[rt][0] && ! son[rt][1] ) {rotate ( rt, 1 );delet ( son[rt][1], x );}else if ( ! son[rt][0] && son[rt][1] ) {rotate ( rt, 0 );delet ( son[rt][0], x );}else {int d = rd[son[rt][0]] > rd[son[rt][1]];rotate ( rt, d );delet ( son[rt][d], x );}}push_up ( rt ); }int search_rank ( int rt, int x ) {if ( ! rt )return 0;if ( key[rt] == x )return siz[son[rt][0]] + 1;if ( key[rt] < x )return siz[son[rt][0]] + cnt[rt] + search_rank ( son[rt][1], x );return search_rank ( son[rt][0], x ); }int find ( int rt, int x ) {if ( ! rt )return 0;if ( siz[son[rt][0]] >= x )return find ( son[rt][0], x );else if ( siz[son[rt][0]] + cnt[rt] < x )return find ( son[rt][1], x - cnt[rt] - siz[son[rt][0]] );elsereturn key[rt]; }int pre ( int rt, int x ) {if ( ! rt )return -INF;if ( key[rt] >= x )return pre ( son[rt][0], x );elsereturn max ( key[rt], pre ( son[rt][1], x ) ); }int suf ( int rt, int x ) {if ( ! rt )return INF;if ( key[rt] <= x )return suf ( son[rt][1], x );elsereturn min ( key[rt], suf ( son[rt][0], x ) ); }int main() {scanf ( "%d", &n );int root = 0;while ( n -- ) {int opt, x;scanf ( "%d %d", &opt, &x );switch ( opt ) {case 1 : insert ( root, x );break;case 2 : delet ( root, x );break;case 3 : printf ( "%d\n", search_rank ( root, x ) );break;case 4 : printf ( "%d\n", find ( root, x ) );break;case 5 : printf ( "%d\n", pre ( root, x ) );break;case 6 : printf ( "%d\n", suf ( root, x ) );break;}}return 0; }如果有任何問題歡迎提出,bye,讓我們與非旋treap不見不散
總結(jié)
以上是生活随笔為你收集整理的带旋treap概念及模板,带例题:普通平衡树的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 千兆无线路由器怎么选千兆路由器应该怎么设
- 下一篇: [非旋平衡树]fhq_treap概念及模