hihocoder #1329 : 平衡树·Splay
#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)的回答,保證一定有合法的解
樣例輸入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 nodesplay中的刪除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:
總結(jié)
以上是生活随笔為你收集整理的hihocoder #1329 : 平衡树·Splay的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Excel和Word 简易工具类,JEa
- 下一篇: Spring Cloud 入门 之 Zu