日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

hihocoder #1329 : 平衡树·Splay

發(fā)布時(shí)間:2025/3/16 编程问答 19 豆豆
生活随笔 收集整理的這篇文章主要介紹了 hihocoder #1329 : 平衡树·Splay 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

#1329 : 平衡樹·Splay

時(shí)間限制:10000ms 單點(diǎn)時(shí)限:1000ms 內(nèi)存限制:256MB

描述

小Ho:小Hi,上一次你跟我講了Treap,我也實(shí)現(xiàn)了。但是我遇到了一個(gè)關(guān)鍵的問(wèn)題。

小Hi:怎么了?

小Ho:小Hi你也知道,我平時(shí)運(yùn)氣不太好。所以這也反映到了我寫的Treap上。

小Hi:你是說(shuō)你隨機(jī)出來(lái)的權(quán)值不太好,從而導(dǎo)致結(jié)果很差么?

小Ho:就是這樣,明明一樣的代碼,我的Treap運(yùn)行結(jié)果總是不如別人。小Hi,有沒有那種沒有隨機(jī)因素的平衡樹呢?

小Hi:當(dāng)然有了,這次我就跟你講講一種叫做Splay的樹吧。而且Splay樹能做到的功能比Treap要更強(qiáng)大哦。

小Ho:那太好了,你快告訴我吧!

輸入

第1行:1個(gè)正整數(shù)n,表示操作數(shù)量,100≤n≤200,000

第2..n+1行:可能包含下面3種規(guī)則:

1個(gè)字母'I',緊接著1個(gè)數(shù)字k,表示插入一個(gè)數(shù)字k到樹中,1≤k≤1,000,000,000,保證每個(gè)k都不相同

1個(gè)字母'Q',緊接著1個(gè)數(shù)字k。表示詢問(wèn)樹中不超過(guò)k的最大數(shù)字

1個(gè)字母'D',緊接著2個(gè)數(shù)字a,b,表示刪除樹中在區(qū)間[a,b]的數(shù)。

輸出

若干行:每行1個(gè)整數(shù),表示針對(duì)詢問(wèn)的回答,保證一定有合法的解

樣例輸入
6 I 1 I 2 I 3 Q 4 D 2 2 Q 2
樣例輸出
3

1


第一次領(lǐng)略到splay tree的厲害。。

小Hi:Splay樹,中文名一般叫做伸展樹。

和Treap樹相同,作為平衡樹,它也是通過(guò)左旋和右旋來(lái)調(diào)整樹的結(jié)構(gòu)。

這里我們?cè)購(gòu)?fù)習(xí)一下左旋和右旋操作:

若以x作為參數(shù)(注意上一講中是以p作為參數(shù)),其對(duì)應(yīng)的偽代碼分別為:

right-rotate(x):p = x.fatherx.father = p.fatherIf (p.father is not empty) ThenIf (p.father.left == p) Thenp.father.left = xElsep.father.right = xEnd IfElseroot = xEnd Ifp.left = x.rightx.right.father = px.right = pp.father = xleft-rotate(x):p = x.fatherx.father = p.fatherIf (p.father is not empty) ThenIf (p.father.left == p) Thenp.father.left = xElsep.father.right = xEnd IfElseroot = xEnd Ifp.right = x.leftx.left.father = px.left = pp.father = x

和Treap樹不同的是,Splay樹不再用一個(gè)隨機(jī)的權(quán)值來(lái)進(jìn)行平衡,而是用固定的調(diào)整方式來(lái)使得調(diào)整之后的樹會(huì)比較平衡。

在左旋右旋的基礎(chǔ)上,Splay樹定義了3個(gè)操作:

1. Zig

直接根據(jù)x節(jié)點(diǎn)的位置,進(jìn)行左旋或右旋。

該操作將x節(jié)點(diǎn)提升了一層。


2. Zig-Zig

若p不是根節(jié)點(diǎn),還有父親節(jié)點(diǎn)g,且p和x同為左兒子或右兒子,則進(jìn)行Zig-Zig操作:

當(dāng)x,p同為左兒子時(shí),依次將p和x右旋;

當(dāng)x,p同為右兒子時(shí),依次將p和x左旋。

注意此處不是將x連續(xù)Zig兩次。該操作將x節(jié)點(diǎn)提升了兩層。


3. Zig-Zag

若p不是根節(jié)點(diǎn),則p還有父親節(jié)點(diǎn)g。且p和x同為左兒子或右兒子,則進(jìn)行Zig-Zag操作:

當(dāng)p為左兒子,x為右兒子時(shí),將x節(jié)點(diǎn)先左旋再右旋;

當(dāng)p為右兒子,x為左兒子時(shí),將x節(jié)點(diǎn)先右旋再左旋。

該操作將x節(jié)點(diǎn)提升了兩層。


進(jìn)一步在Zig,Zig-Zig和Zig-Zag操作上,Splay樹定義了"Splay"操作。

對(duì)于x以及x的祖先y,splay(x, y),表示對(duì)x節(jié)點(diǎn)進(jìn)行調(diào)整,使得x是y的兒子節(jié)點(diǎn):

splay(x, y):While (x.father != y)p = x.fatherIf (p.father == y) Then// 因?yàn)閜的父親是y,所以只需要將x進(jìn)行Zig操作// 就可以使得x的父親變?yōu)閥If (p.left == x) Thenright-rotate(x)Elseleft-rotate(x)End IfElseg = p.fatherIf (g.left == p) ThenIf (p.left == x) Then// x,p同為左兒子,Zig-Zig操作right-rotate(p)right-rotate(x)Else// p為左,x為右,Zig-Zag操作left-rotate(x)right-rotate(x)End IfElseIf (p.right == x) Then// x,p同為右兒子,Zig-Zig操作left-rotate(p)left-rotate(x)Else // p為右,x為左,Zig-Zag操作right-rotate(x)left-rotate(x)End IfEnd IfEnd IfEnd While

在執(zhí)行這個(gè)操作的時(shí)候,需要保證y節(jié)點(diǎn)一定是x節(jié)點(diǎn)祖先。

值得一提的是,大多數(shù)情況下我們希望通過(guò)splay操作將x旋轉(zhuǎn)至整棵樹的根節(jié)點(diǎn)。此時(shí)只需令y=NULL即可實(shí)現(xiàn)。


小Ho:旋轉(zhuǎn)和Splay我懂了,但是要怎么運(yùn)用上去呢?

小Hi:Splay樹的插入和查詢操作和普通的二叉搜索樹沒有什么大的區(qū)別,需要注意的是每次插入和查詢結(jié)束后,需要對(duì)訪問(wèn)節(jié)點(diǎn)做一次Splay操作,將其旋轉(zhuǎn)至根。

insert(key):node = bst_insert(key) // 同普通的BST插入, node為當(dāng)前插入的新節(jié)點(diǎn)splay(node, NULL)find(key):node = bst_find(key) // 同普通的BST查找, node為查找到的節(jié)點(diǎn)splay(node, NULL)

同時(shí)由于Splay的特性,我們還有兩個(gè)特殊的查詢操作。在樹中查找指定數(shù)key的前一個(gè)數(shù)和后一個(gè)數(shù)。

我們先將key旋轉(zhuǎn)至根,那么key的前一個(gè)數(shù)一定是根節(jié)點(diǎn)左兒子的最右子孫,同時(shí)key的后一個(gè)數(shù)一定是根節(jié)點(diǎn)右兒子的最左子孫。

findPrev(key):splay( find(key), NULL )node = root.leftWhile (node.right)node = node.rightReturn nodefindNext(key):splay( find(key), NULL )node = root.rightWhile (node.left)node = node.leftReturn node

splay中的刪除key操作:

splay的刪除可以采用和一般二叉搜索樹相同的方法:即先找到節(jié)點(diǎn)key,若key沒有兒子則直接刪去;若key有1個(gè)兒子,則用兒子替換掉x;若key有2個(gè)兒子,則通過(guò)找到其前(或后)一個(gè)節(jié)點(diǎn)來(lái)替換掉它,最后將該節(jié)點(diǎn)Splay到根。

同時(shí),這里還有另一種方法來(lái)完成刪除操作:

首先我們查找到key的前一個(gè)數(shù)prev和后一個(gè)數(shù)next。將prev旋轉(zhuǎn)至根,再將next旋轉(zhuǎn)為prev的兒子。

此時(shí)key節(jié)點(diǎn)一定是next的左兒子。那么直接將next的左兒子節(jié)點(diǎn)刪去即可。

delete(key):prev = findPrev(key)next = findNext(key)splay(prev, NULL)splay(next, prev)next.left = NULL

這里你可能會(huì)擔(dān)心如果key是數(shù)中最小或者是最大的數(shù)怎么辦?

一個(gè)簡(jiǎn)單的處理方式是手動(dòng)加入一個(gè)超級(jí)大和超級(jí)小的值作為頭尾。


那么小Ho,這里有一個(gè)問(wèn)題,假如要?jiǎng)h除一個(gè)區(qū)間[a,b]的數(shù)該怎么做?

小Ho:我想想...我知道了!

因?yàn)橐獎(jiǎng)h除[a,b],那么我就要想辦法把[a,b]的數(shù)旋轉(zhuǎn)到一個(gè)子樹上,再將這個(gè)子樹刪掉就行了。

方法和刪除一個(gè)數(shù)相同,我首先將a的前一個(gè)數(shù)prev和b的后一個(gè)數(shù)next找出來(lái)。

同樣將prev旋轉(zhuǎn)至根,再將next旋轉(zhuǎn)為prev的兒子。

那么此時(shí)next的左子樹一定就是所有[a,b]之間的數(shù)了!

deleteInterval(a, b):prev = findPrev(a)next = findNext(b)splay(prev, NULL)splay(next, prev)next.left = NULL

小Hi:沒錯(cuò),那么下一個(gè)問(wèn)題!如果a,b不在樹中呢?

小Ho:這還不簡(jiǎn)單,把a(bǔ),b插入樹中,做完之后再刪除不就好了!

小Hi:想不到小Ho你還蠻機(jī)智的嘛。

小Ho:那是,畢竟是我小Ho。(哼哼)

小Hi:Splay樹由于splay操作的使得其相較于Treap具有更大的靈活性,并且不再有隨機(jī)性。其插入、查找和刪除操作的均攤時(shí)間復(fù)雜度也都是O(logn)的,具體的復(fù)雜度分析可以參考這里。那么最后小Ho你能夠把Splay的實(shí)現(xiàn)出來(lái)么?

小Ho:沒問(wèn)題,看我的吧!


AC:

#include<iostream> #include<cstdio> #include<cstring> using namespace std;const int inf = 0x3f3f3f3f; struct Node {int val;struct Node *father;struct Node *left,*right;Node(){}Node(int key) {this->val = key;this->father = this->left = this->right = NULL;} }; Node *root;void left_rotate(Node *x) {Node *p = x->father;x->father = p->father;if(p->father != NULL) { //左旋if(p->father->left == p) {p->father->left = x;} else if(p->father->right == p) {p->father->right = x;}} else { //說(shuō)明此時(shí)的p是根節(jié)點(diǎn)root = x;}p->right = x->left;if(x->left != NULL)x->left->father = p;x->left = p;p->father = x; }void right_rotate(Node *x) {Node *p = x->father;x->father = p->father;if(p->father != NULL) { //右旋if(p->father->left == p) {p->father->left = x;} else if(p->father->right == p) {p->father->right = x;}} else { //說(shuō)明此時(shí)的p是根節(jié)點(diǎn)root = x;}p->left = x->right;if(x->right != NULL)x->right->father = p;x->right = p;p->father = x; }void Splay(Node *x,Node *y) { //表示對(duì)x節(jié)點(diǎn)進(jìn)行調(diào)整,使得x是y的兒子節(jié)點(diǎn)while(x->father != y) {Node *p = x->father;if(p->father == y) {// 因?yàn)閜的父親是y,所以只需要將x進(jìn)行Zig操作// 就可以使得x的父親變?yōu)閥if(p->left == x) {right_rotate(x);} else {left_rotate(x);}} else {Node *g = p->father;if(p == g->left) {if(x == p->left) { // x,p同為左兒子,Zig-Zig操作//當(dāng)x,p同為左兒子時(shí),依次將p和x右旋//當(dāng)x,p同為右兒子時(shí),依次將p和x左旋right_rotate(p);right_rotate(x);} else { // p為左,x為右,Zig-Zag操作//當(dāng)p為左兒子,x為右兒子時(shí),將x節(jié)點(diǎn)先左旋再右旋//當(dāng)p為右兒子,x為左兒子時(shí),將x節(jié)點(diǎn)先右旋再左旋left_rotate(x);right_rotate(x);}} else {if(x == p->right) {// x,p同為右兒子,Zig-Zig操作left_rotate(p);left_rotate(x);} else {// p為右,x為左,Zig-Zag操作right_rotate(x);left_rotate(x);}}}} }Node *bst_insert(int key) { //類似平衡二叉樹先插入節(jié)點(diǎn)Node *p = new Node(key);if(root == NULL) {root = p;return root;}Node *t = root;while(true) {if(key > t->val) { //右枝走if(t->right == NULL) {t->right = p;p->father = t;break;}t = t->right;} else if(key < t->val) { //左枝走if(t->left == NULL) {t->left = p;p->father = t;break;}t = t->left;} else return t; //說(shuō)明有重復(fù)元素}return p; }Node *bst_find(int key) { //普通的平衡二叉樹的查找Node *t = root;while(t->val != key) {if(key > t->val) {if(t->right != NULL) { t = t->right;} else return NULL; } else {if(t->left != NULL) {t = t->left;} else return NULL;}}return t; }void insert(int key) {Node *p = bst_insert(key);Splay(p,NULL); }Node *find(int key) {Node *p = bst_find(key);if(p != NULL) {Splay(p,NULL);}return p; }Node *findPre(int key) { //查找指定數(shù)key的前一個(gè)數(shù)Node *p = find(key);if(p == NULL) return NULL;Splay(p,NULL);Node *t = root->left;if(t == NULL) return root;while(t->right) {t = t->right;}return t; }Node *findNext(int key) {Node *p = find(key);if(p == NULL) return NULL;Splay(p,NULL);Node *t = root->right;if(t == NULL) return root;while(t->left) {t = t->left;}return t; }void Delete(int key) {Node *pre = findPre(key);Node *next = findNext(key);if(pre != NULL && next != NULL) {Splay(pre,NULL);Splay(next,pre);next->left = NULL;} }void deleteInterval(int a,int b) {insert(a);insert(b);Node *pre = findPre(a);Node *next = findNext(b);Splay(pre,NULL);Splay(next,pre);next->left = NULL;Delete(a);Delete(b); }int main() {int n,k,a,b;char op[2];while(scanf("%d",&n)!=EOF) {root = NULL;insert(-1);insert(inf);while(n--) {scanf("%s",op);if(op[0] == 'I') {scanf("%d",&k);insert(k);} else if(op[0] == 'Q') {scanf("%d",&k);Node *p = find(k);if(p != NULL) {printf("%d\n",k);} else {insert(k);p = findPre(k);Delete(k);printf("%d\n",p->val);}} else {scanf("%d %d",&a,&b);deleteInterval(a,b);}}}return 0; }

總結(jié)

以上是生活随笔為你收集整理的hihocoder #1329 : 平衡树·Splay的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。