Treap平衡树
作者:互联网
1.1
treap随机生成优先值
令以优先值为小根堆heap
其关键值为二叉搜索树tree
二叉搜索树满足性质:
节点p的左子树内所有的关键值都小于等于p的关键值,
节点p的右子树内所有的关键值都大于p的关键值。
二叉搜索树可以方便地查到节点p的排名,以及查排名为k的节点编号
问题在于,如果有序地输入一个序列,那树就会退化为一条链,树的深度变为$n$,每次的操作复杂度变为$O(n)$
对此,出现了二叉搜索树的优化——平衡树。平衡树可以通过一些操作使得树的深度稳定在$O(log n)$
Treap通过两个操作进行优化,spilt(分裂)和merge(合并)
分裂操作:保证堆到左边的点的数值都小于等于分裂值,右边的点都大于分裂值
初始树:
当分裂值为3时,分裂的步骤如下:
第一步:找到当前的根:
由于节点$1$的数值为$5$,大于分裂值$3$,所以节点$1$及其右子树堆到右边去
第二步:由于上一步的根大于分裂值,所以来到了上一步根的左子树
来到了$2$号节点,由于$2$号节点的数值为$3 = 3$,所以其与其左子树一起堆到左边
第三步:由于上一步的根小于等于分裂值,所以来到了上一步根的右子树
找到了节点$5$,由于其值为$4$,大于分裂值$3$,所以将其堆到右边
将节点$5$接到左子树上
这样就分裂完毕,得到了两棵树
代码如下:
void split(int p,int v,int &x,int &y){ //p这棵树按照v分裂,x和y是下一层分裂后两棵树的树根 if( !p ){ //如果没有这棵树根 x = y = 0;//没有分裂后的两棵树 return; } if( v < val[p] ){ //如果节点p的数值比按照值大 y = p;//改了上一层的ch[p][0] split( ch[p][0],v,x,ch[p][0]);//分裂左子树,左堆不动,右堆 }else{ x = p;//同上 split( ch[p][1],v,ch[p][1],y ); } pushup(p);//重新计算p的大小 }
合并:以wei[ ]数组按照heap的性质合并
如对于上图的结果,将两棵树合并的过程如下:
第一步:比较两树树根的$wei$值
其中节点$2$的$wei[2] = 10,wei[5] = 7$,所以新树的根为节点$1$,并且把其右子树也一起搞过去
第二步:比较 右堆的左子树 与 左堆的根 的$wei$的值
因为$wei[5] = 21,wei[2] = 10 -> $,节点$2$的值小,所以将左堆的根与其左子树一起接到新树的左子树上
第三步:再比较 左堆的右子树 与 右堆的左子树 的$wei$值
由于左堆已经没有了右子树,所以直接将右堆的$5$号点加进当前节点($2$号点)的右子树
代码如下:
int merge(int x,int y){ if( !x || !y ) return x|y;//如果有一棵子树缺失,返回另一棵子树 if( wei[x] < wei[y] ){ //左边的优先值小的话 ch[x][1] = merge( ch[x][1],y );//将左边的右子树与右边合并,左子树照常不管了 pushup(x);//重新计算左边的大小 return x;//左堆的根当做新树的当前的根 }else{ //右边优先值小 ch[y][0] = merge( x,ch[y][0] );//同理,将右边的左子树跟左堆 pushup(y); return y; } }
删除:将整个树按照三个类型,拆成三个部分,再合并
我们将可以这个树按照三个标准:“小于删除值”、“等于删除值”、“大于删除值”分为三棵树
三棵树的树根分别为$x$,$y$,$z$,那么我们就只需要删除$y$树的根节点啦
先上代码:
void del( int p ){ int x,y,z; split( rt,p,x,z ); split( x,p-1,x,y ); if( y ){ //如果存在我们要删除的这个元素 y = merge( ch[y][0],ch[y][1] ); //去掉根节点,也就是让这个树的左右子树合并 } x = merge( x,y ); rt = merge( x,z ); }
解释一下代码:
比如下面这个图,我们要删除$3$这个节点
先按照$3$拆第一步:
再按照$2$拆第二步
合并y树的两个儿子节点
合并x,y树:
再合并一下x,z树即可(不再模拟)
插入:将原来的树分为两部分,再与新节点合并
用于插入一个权值为$p$的节点
我们按照$p$将原来的树分裂,这时$x$中的节点都小于等于$p$,$y$中的节点都大于$p$
再将新节点与$x,y$两棵树合并即可
代码如下:
void add( int p ){ //插入节点p int x,y; split( rt,p,x,y ); x = merge( nownode(p),x ); rt = merge( x,y ); }
给数值求排名:类似搜索树,但不完全是
如果我们要查询数值为$p$的节点的排名,可以将树按照$p$分裂
然后答案就是$x$树的大小+1(加上自己才是最终排名)
代码如下:
int getrank( int p ){ //查询值为p的排名 int x,y; split( rt,p,x,y ); int rank = size[x]+1; rt = merge( x,y ); return ans; }
给排名求数值:完全就是二叉搜索树
不再解释
上代码:
int getval( int rank ){ int now = rt; while( now ){ if( size[now] == rank ) return val[now]; if( size[now] > rank ) now = ch[now][0];//如果排名大了,说明前面的人多,往左 else{ //如果排名小了,说明前面的少,往右 rank -= size[ ch[now][0] ] + 1; //往右走说明当前节点以及其左子树必然被包含 //往右走后树内将不再包含当前节点以及其左子树 //所以rank要减去当前节点及其左子树的大小 now = ch[ch][1]; } } return INF;//没有查到目标排名,返回没有节点 }
求前驱后继
也基本就是二叉搜索树
分裂后一直往一个方向搜即可
代码:
int getfre( int p ){ int x,y; splite( rt,p-1,x,y ); int now = x; while( ch[now][1] ) now = ch[now][1]; ans = val[now]; rt = merge( x,y ); return ans; } int getnxt( int p ){ int x,y; splite( rt,p,x,y ); int now = y; while( ch[now][0] ) now = ch[now][0]; ans = val[now]; rt = merge( x,y ); return ans; }
标签:左子,ch,merge,int,Treap,平衡,now,节点 来源: https://www.cnblogs.com/xiao-en/p/16511143.html