来自《算法竞赛入门经典训练指南》
1.Treap实现名次树
1.简单介绍
Treap是一棵拥有键值和优先级两种权值的树。对于键值而言,这棵树是二叉排序树。对于优先级而言,这棵树是堆,即在这棵树的任意子树中,根的优先级是最大的。
不难证明,如果每个结点的优先级事先给定且互不相等,整棵树的形态也就唯一确定了,和元素的插入顺序无关。在Treap的插入算法中,每个节点的优先级是随机确定的。
因此各个操作的时间复杂度也是随机的。幸运的是,可以证明插入、删除和查找的期望时间复杂度均为O(nlogn)。
2.Treap结点的定义
struct Node
{
Node *ch[2];//左右子树
int r;//优先级,数值越大,优先级越高
int v;//值
int s;//结点总数
int cmp(int x) const{
if(x==v) return -1;
return x<v?0:1;
}
};
3.旋转
分为左旋和右旋
//d=0表示左旋,d=1表示右旋
void rotate(Node* &o,int d)
{
Node* k=o->ch[d^1];
o->ch[d^1]=k->ch[d];
k->ch[d]=o;
o=k;
}
注意小技巧:因为d=0或者1,d^1定价于1-d,但计算速度更快。关于左旋或者右旋,可自行百度……
4.插入结点
插入结点时,先随机给新结点一个优先级,然后执行普通的插入算法(根据键值大小判断插入哪颗子树中。执行完毕后,用左右旋,维护Treap树的堆性质。
如要采用递归实现,则只需在递归插入之后判断是否需要旋转。
//在以o为根的子树中插入键值x,修改o
void insert(Node* &o,int x)
{
if(o==NULL){
o=new Node();
o->ch[0]=o->ch[1]=NULL;
o->v=x;
o->r=rand();
}
else{
int d=o->cmp(x);
insert(o->ch[d],x);
if(o->ch[d]->r>o->r){
rotate(o,d^1);
}
}
}
5.删除结点
删除结点,如果只有一颗子树,直接用这棵子树代替待删除结点成为根即可(注意o是叶子的情况也符合在内)。麻烦的是o有两棵子树的情况,我们先把优先级较高的一颗旋转到根,然后递归在另一棵子树中删除结点o。例如,左子结点的优先级较高,就必须进行右旋,否则会违反堆性质。
void remove(Node* &o,int x)
{
int d=o->cmp(x);
if(d==-1){
if(o->