无旋平衡树(treap)
引子
在平衡树算法中,大多需要进行旋转操作,但旋转操作细节较多,容易写错,并且不好调试。所以我们需要不用旋转的平衡树——分裂合并 treap
那么这种平衡树是如何维持平衡的呢?容易想到,在建立包含一个序列中的数字的树时,如果能够随机选择元素插入的次序,那么所建立的树应当是接近平衡的,而由于我们建立的树需要支持插入,删除等操作,因此需要在每个节点建立时随机选择一个平衡因子,在之后的操作中维持树上的平衡因子最小堆或最大堆的性质,这样就可以实现期望平衡
实现
基本操作
人如其名,分裂合并treap的核心操作就是分裂与合并。
Split
在分裂操作中,我们所做的操作是对于给定的权值k,将当前的树分裂为权值大于k和小于等于k两部分。
void split(int now,int k,int &x,int &y)\\x,y分别表示当前权值小于等于k的子树的根节点和大权值大于k的子树的根节点
{
if(!now)x=y=0;//若已经搜索到树叶,则直接返回
else
{
if(val[now]<=k)
x=now,split(ch[now][1],k,ch[now][1],y);//如果当前节点的权值小于等于k,则小于等于k的子树的根节点即为当前节点,同时继续遍历右子树
else
y=now,split(ch[now][0],k,x,ch[now][0]);//同上
update(now);
}
}
Merge
在合并操作中,所做的操作是对于两棵树x,y(x中所有节点的权值小于y中所有结点的权值
int merge(int x,int y)//y中的所有权值大于x中的权值
{
if(!x||!y)return x+y;//如果需要合并的一个树为空,则直接返回另一个待合并树
else
{
if(pri[x]<pri[y])//维护平衡因子小根堆性质
{
ch[x][1]=merge(ch[x][1],y);//将y与x右子树合并
update(x);return x;
}
else
{
ch[y][0]=merge(x,ch[y][0]);//将x与y左子树合并
update(y);return y;
}
}
}
<