一、簡介
無旋Treap(fhq_treap),是一種不用旋轉的treap,其代碼復雜度不高,應用范圍廣(能代替普通treap和splay的所有功能),是一種極其強大的平衡樹。
無旋Treap是一個叫做范浩強的大佬發明的(快%啊!)
在我們一起學習無旋Treap之前,本蒟蒻有幾句活想說:
1.無旋Treap我個人認為是最容易理解的一種平衡樹,而且編程復雜度不高,功能還那么強大。
我一開始學平衡樹的時候是先從普通的帶旋轉的Treap開始學的。那種Treap,我現在都沒搞懂什么左旋右旋究竟是怎么一回事。
我當時真的以為我這輩子都不指望學會平衡樹了,直到djq大佬(快%!)告訴了我這種很美妙的數據結構。
我真就不明白了,為什么那么多人願意去學普通的Treap而不學無旋Treap。無旋Treap更容易理解,最主要的是他與普通的Treap相比能可持久化!(盡管我不會)
所以,在大家學習完無旋Treap以后,本蒟蒻請諸君不妨推廣一下無旋Treap,造福更多的Oier。
2.關於無旋Treap和其他平衡樹的比較:(這個建議大家學完無旋Treap再來看,可能感觸會更深刻一些)
與AVL相比:旋轉操作真的很浪費時間,最壞情況下復雜度為O(log n),而且AVL樹難寫無比,不適合運用於算法競賽。
與普通Treap相比:參見第一條
與splay相比:基本上可以代替splay的所有功能,但是在處理LCT問題上沒有splay優秀
與rbt(紅黑樹相比):紅黑樹特別難寫是眾所周知的。
與sbt相比:sbt是我認為的最強大的平衡樹。但是無旋Treap中的merge、split操作的應用的廣泛(可持久化Treap維護Hash之類的)是sbt做不到的。
二、Treap
什么是Treap?
Treap=Tree+heap
相信大家都知道二叉堆吧。父節點的權值比子節點都要大(或小)
而Treap,則是在BST(二叉查找樹)的基礎上,添加二叉堆中的這個元素。
Treap與heap的區別是,heap是完全二叉樹,而Treap不是。
下面的
三、核心操作
我在前面說過,普通的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,其他平衡樹的基本操作就好做多了
三、其他操作
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 【模板】普通平衡樹
如有不足請指正,謝謝