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 【模板】普通平衡树
若有不足请指正,谢谢