1、簡介算法
無旋Treap(fhq_treap),是一種不用旋轉的treap,其代碼複雜度不高,應用範圍廣(能代替普通treap和splay的全部功能),是一種極其強大的平衡樹。編程
無旋Treap是一個叫作範浩強的大佬發明的(快%啊!)數據結構
在咱們一塊兒學習無旋Treap以前,本蒟蒻有幾句活想說:函數
1.無旋Treap我我的認爲是最容易理解的一種平衡樹,並且編程複雜度不高,功能還那麼強大。學習
我一開始學平衡樹的時候是先從普通的帶旋轉的Treap開始學的。那種Treap,我如今都沒搞懂什麼左旋右旋到底是怎麼一回事。url
我當時真的覺得我這輩子都不期望學會平衡樹了,直到djq大佬(快%!)告訴了我這種很美妙的數據結構。spa
我真就不明白了,爲何那麼多人願意去學普通的Treap而不學無旋Treap。無旋Treap更容易理解,最主要的是他與普通的Treap相比能可持久化!(儘管我不會).net
因此,在你們學習完無旋Treap之後,本蒟蒻請諸君不妨推廣一下無旋Treap,造福更多的Oier。code
2.關於無旋Treap和其餘平衡樹的比較:(這個建議你們學完無旋Treap再來看,可能感觸會更深入一些)blog
與AVL相比:旋轉操做真的很浪費時間,最壞狀況下複雜度爲O(log n),並且AVL樹難寫無比,不適合運用於算法競賽。
與普通Treap相比:參見第一條
與splay相比:基本上能夠代替splay的全部功能,可是在處理LCT問題上沒有splay優秀
與rbt(紅黑樹相比):紅黑樹特別難寫是衆所周知的。
與sbt相比:sbt是我認爲的最強大的平衡樹。可是無旋Treap中的merge、split操做的應用的普遍(可持久化Treap維護Hash之類的)是sbt作不到的。
2、Treap
什麼是Treap?
Treap=Tree+heap
相信你們都知道二叉堆吧。父節點的權值比子節點都要大(或小)
而Treap,則是在BST(二叉查找樹)的基礎上,添加二叉堆中的這個元素。
Treap與heap的區別是,heap是徹底二叉樹,而Treap不是。
下面的
3、核心操做
我在前面說過,普通的Treap最煩人的地方即是旋轉。
而無旋Treap是如何作到無旋的呢?
關鍵就在兩個操做:merge和split
1.split
split,顧名思義,就是把一個平衡樹分紅兩棵樹。
split有兩種:一種是按照權值split,一種是按照size來split。
若是按照權值split,那麼分出來兩棵樹的第一棵樹上的每個數的大小都小於(或小於等於,視具體狀況而定)x;
若是按照size split,那麼分出來兩棵樹的第一棵樹剛好有x個節點。
咱們能夠結合具體代碼講解:
inline void split(int k,int& l,int& r,int x){//理解時,咱們能夠把l當作是答案的第一個,r當作答案的第二個,這個函數的意義是:將以k爲根的樹按照val分爲以l爲根的樹和以r爲根的樹。注意引用的做用 if(!k){ l=r=0;//分到底了,返回 return; } if(tree[k].val<x){//若是比它小 l=k;//那麼x確定在k的右子樹裏,先將k貼到第一個答案上 split(tree[l].r,tree[l].r,r,x);//把第一個答案的右子樹按x分開,獲得答案(這裏本身理解一下,不難懂) }else{//反之亦然 r=k; split(tree[r].l,l,tree[r].l,x); } push_up(k); }
而按size split的道理是同樣的:
inline void split(int k,int& l,int& r,int x){ if(!k){ l=r=0; return; } if(tree[tree[k].l].size+1<=x){ l=k; split(tree[l].r,tree[l].r,r,x-tree[tree[k].l].size-1);//注意這裏有些變化 }else{ r=k; split(tree[r].l,l,tree[r].l,x); } push_up(k); }
這樣就把一棵樹分開了。
2.merge
merge就是把兩顆本來分開的樹合併在一塊兒。
咱們仍然結合具體代碼講解
inline void merge(int& k,int l,int r){//函數名的意義是:把以l爲根的樹和以r爲根的樹合併爲以k爲根的樹 if(!l||!r){//合併到底,返回 k=l+r; return; } if(tree[l].p>tree[r].p){//默認大根堆 k=l;//先把l掛到k上 merge(tree[k].r,tree[k].r,r);//注意要知足BST性質 }else{//反之亦然 k=r; merge(tree[k].l,l,tree[k].l); } push_up(k); }
有了merge和split,其餘平衡樹的基本操做就好作多了
3、其餘操做
inline void insert(int val){ int x,y; split(root,x,y,val-1); merge(x,x,New(val)); merge(root,x,y); } inline void Delete(int val){ int x,y,z; split(root,x,y,val); split(x,x,z,val-1); merge(z,tree[z].l,tree[z].r); merge(x,x,z); merge(root,x,y); } inline int rnk(int val){ int x,y; split(root,x,y,val-1); int ans=tree[x].size+1; merge(root,x,y); return ans; } inline int kth(int k){ int x=root; while(true){ if(k==tree[tree[x].l].size+1) return tree[x].val; if(k<=tree[tree[x].l]) x=tree[x].l; else k-=tree[tree[x].l].size+1,x=tree[x].r; } } inline int pre(int val){ int x,y; split(root,x,y,val-1); int ans=tree[x].val; merge(root,x,y); return ans; } inline int suf(int val){ int x,y; split(root,x,y,val); int ans=kth(tree[x].size+1); merge(root,x,y); return ans; }
最後給你們放上一道模板題P3369 【模板】普通平衡樹
若有不足請指正,謝謝