想必大家都知道一种叫做二叉搜索树这东西吧,那么我们知道,在某些特殊情况下,二叉搜索树会退化成一条链,而且如果出题人成心想卡你的话也很简单,分分钟把你(n log n)的期望卡成。那么我们该如何避免这种尴尬状况的出现呢?我们的范浩强大佬就创造了一种名为Treap的算法。
那么这个算法是如何实现的呢?
首先,我们发现:
当我们将一组数放入一棵二叉搜索树的顺序改变时,那么你最终得到的二叉搜索树也会发生变化。
这便是Treap算法优化二叉搜索树的突破口。这二叉搜索树的这一个特点也就意味着我们完全可以将一棵已经退化成一条链的二叉搜索树优化成一个两边相对平衡的二叉搜索树。
在这里,我隆重介绍一种在一颗二叉搜索树中可谓BUG级别的操作——左/右旋。
这个操作可以在满足一颗二叉搜索树性质的同时交换父亲与儿子的位置,也就是说这个操作可以逆天到让爸爸给他儿子当儿子,儿子当爸爸,而且还是被法律所允许的!!!(想想就恐(ji)怖(dong))嗯,具体操作如下图:
a,b,c分别为三个节点,其中我们要交换的是b节点和c节点。t为a的整颗左子树,x为b的整颗左子树,y为c的整颗左子树,z为c的整颗右子树。可以看见,右图由左图左旋得到,而左图就是由右图右旋得到的。
学会了这个操作也就意味这我们可以对这颗二叉搜索树为所欲为了!
所以我们现在所要考虑的就是如何做到让其相对平衡?
其实答案很简单,就是给每个数随机一个第二关键字,并且在满足一颗二叉搜索树性质的同时,用堆来维护这个第二关键字(我这里采用了大根堆),然后我们便能够得到一颗相对平衡的二叉搜索树了!
这也便是Treap这个名字的由来:Tree(树)+Heap(堆)
1、废话不多说,先给大家附上我的变量定义
type
treap=record
ch:array[-1..1]of longint;
key,fix,cnt,size,father:longint;
//ch数组表示它连接的儿子节点ch[-1]为左儿子,ch[1]为右儿子(这么定义就是为了偷懒。。。)
//key为第一关键字(就是它的数值)fix为第二关键字(我们随机出来的)
//cnt表示与当前节点数值相同的点的个数,size表示以他为根的子树的大小
//father为它的父亲节点
//注意,因为旋转操作是涉及到祖孙三代的,所以既要记录儿子又要记录父亲。
end;
2、逆天的旋转操作
procedure rotate(x,d:longint);
var
k,father,son:longint;
begin
son:=t[x].ch[d];
father:=t[x].father;
t[son].father:=father;
t[x].father:=son;
if t[father].key<t[x].key then k:=1 else k:=-1;
t[father].ch[k]:=son;
t[x].ch[d]:=t[son].ch[-d];
t[son].ch[-d]:=x;
update(x); update(son);
end;
3、更新操作
procedure update(x:longint);
begin
t[0].size:=0;
t[x].size:=t[x].cnt+t[t[x].ch[-1]].size+t[t[x].ch[1]].size;
end;
4、插入操作
删除操作分为三步:
1、找到该数在Treap中的位置,并将路径上的点size域+1;
2、插入该数,若树中已有该数,则将该数的cnt域+1,否则就新建一个叶子节点;
3、回溯时,判断儿子和父亲的fix(第二关键字)域是否满足堆的性质,若不满足则用旋转操作把父亲结点旋下来,将儿子节点 转上去,使其继续满足堆的性质;
procedure insert(last,x,key:longint);
var
d:longint;
begin
if (x=0)and(last<>0) then //建立新的节点
begin
inc(cnt); x:=cnt;
t[x].cnt:=1; t[x].size:=1;
t[x].key:=key; t[x].fix:=rand;
t[x].father:=last;
if last<>x then
begin
if t[last].key<key then d:=1 else d:=-1;
t[last].ch[d]:=x; update(last);
end;
exit;
end;
inc(t[x].size); //路径上的节点都+1
if t[x].key=key then begin inc(t[x].cnt); exit end; //寻找插入的位置
if t[x].key<key then d:=1 else d:=-1;
insert(x,t[x].ch[d],key);
if t[x].fix<t[t[x].ch[d]].fix then rotate(x,d);
end;
5、删除操作
找到删除结点的位置后有三种情况:
1、该节点有多个,也就是t[x].cnt>1:此时只要将t[x].cnt-1即可。
2、它只有一个儿子:用它的儿子来代替它。
3、它有两个儿子:将它第二关键字值较大(若是小根堆则为较小)的节点旋上来,继续递归,直至到达1或2。
procedure delet(x,key:longint);
var
ls,rs,d:longint;
begin
ls:=t[x].ch[-1]; rs:=t[x].ch[1];
if t[x].key=key then
begin
if t[x].cnt>1 then begin dec(t[x].cnt); dec(t[x].size); exit; end else
if (ls=0)or(rs=0) then
begin
if t[x].key<t[t[x].father].key then d:=-1 else d:=1;
t[t[x].father].ch[d]:=ls+rs;
t[ls+rs].father:=t[x].father;
end else
begin
if t[ls].fix<t[rs].fix then d:=1 else d:=-1;
rotate(x,d); delet(x,key);
end;
end else
begin
if t[x].key<key then d:=1 else d:=-1;
dec(t[x].size); //路径上的节点都-1
delet(t[x].ch[d],key); //找到删除的节点位置
end;
end;
6、查排名
function rank(x,key:longint):longint;
begin
if x=0 then exit(0);
if t[x].key=key then exit(t[t[x].ch[-1]].size+1); //找到了
if t[x].key>key then exit(rank(t[x].ch[-1],key)); //在左子树中
exit(rank(t[x].ch[1],key)+t[t[x].ch[-1]].size+t[x].cnt);
//在右子树中,注意,此时整颗左子树都比它小,所以要+t[t[x].ch[-1]].size
end;
7、查找第k小数
感觉和上面查排名的操作差不多,就是反了一下罢了。
function find_kth(x,k:longint):longint;
var
ls,rs:longint;
begin
while true do
begin
ls:=t[x].ch[-1]; rs:=t[x].ch[1];
if k<=t[ls].size then x:=ls else
if k>t[ls].size+t[x].cnt then
begin
k:=k-t[ls].size-t[x].cnt;
x:=rs;
end else exit(t[x].key);
end;
end;
8、找前驱/后继
前驱就是在它的左子树中找一个最大的数,而后继则为在它的右子树中找一个最小的数。
在注意一下在找最大/最小值是别忘了它的父亲就行。
function find_pred(x,key:longint):longint;
begin
if x=0 then exit(-maxlongint);
if t[x].key>=key then exit(find_pred(t[x].ch[-1],key));
exit(max(find_pred(t[x].ch[1],key),t[x].key));
end;
function find_succ(x,key:longint):longint;
begin
if x=0 then exit(maxlongint);
if t[x].key<=key then exit(find_succ(t[x].ch[1],key));
exit(min(find_succ(t[x].ch[-1],key),t[x].key));
end;
下面附上我完整的代码:
type
treap=record
ch:array[-1..1]of longint;
key,fix,cnt,size,father:longint;
end;
var
t:array[1..10000]of treap;
flag,x,n,cnt,r,i:longint;
function max(a,b:longint):longint;
begin
if a>b then exit(a) else exit(b);
end;
function min(a,b:longint):longint;
begin
if a<b then exit(a) else exit(b);
end;
function rand:longint;
begin
r:=(int64(r)*39916801)mod 4987656;
exit(r);
end;
procedure update(x:longint);
begin
t[0].size:=0;
t[x].size:=t[x].cnt+t[t[x].ch[-1]].size+t[t[x].ch[1]].size;
end;
procedure rotate(x,d:longint);
var
k,father,son:longint;
begin
son:=t[x].ch[d];
father:=t[x].father;
t[son].father:=father;
t[x].father:=son;
if t[father].key<t[x].key then k:=1 else k:=-1;
t[father].ch[k]:=son;
t[x].ch[d]:=t[son].ch[-d];
t[son].ch[-d]:=x;
update(x); update(son);
end;
procedure insert(last,x,key:longint);
var
d:longint;
begin
if (x=0)and(last<>0) then
begin
inc(cnt); x:=cnt;
t[x].cnt:=1; t[x].size:=1;
t[x].key:=key; t[x].fix:=rand;
t[x].father:=last;
if last<>x then
begin
if t[last].key<key then d:=1 else d:=-1;
t[last].ch[d]:=x; update(last);
end;
exit;
end;
inc(t[x].size);
if t[x].key=key then begin inc(t[x].cnt); exit end;
if t[x].key<key then d:=1 else d:=-1;
insert(x,t[x].ch[d],key);
if t[x].fix<t[t[x].ch[d]].fix then rotate(x,d);
end;
procedure delet(x,key:longint);
var
ls,rs,d:longint;
begin
ls:=t[x].ch[-1]; rs:=t[x].ch[1];
if t[x].key=key then
begin
if t[x].cnt>1 then begin dec(t[x].cnt); dec(t[x].size); exit; end else
if (ls=0)or(rs=0) then
begin
if t[x].key<t[t[x].father].key then d:=-1 else d:=1;
t[t[x].father].ch[d]:=ls+rs;
t[ls+rs].father:=t[x].father;
end else
begin
if t[ls].fix<t[rs].fix then d:=1 else d:=-1;
rotate(x,d); delet(x,key);
end;
end else
begin
if t[x].key<key then d:=1 else d:=-1;
dec(t[x].size); delet(t[x].ch[d],key);
end;
end;
function rank(x,key:longint):longint;
begin
if x=0 then exit(0);
if t[x].key=key then exit(t[t[x].ch[-1]].size+1);
if t[x].key>key then exit(rank(t[x].ch[-1],key));
exit(rank(t[x].ch[1],key)+t[t[x].ch[-1]].size+t[x].cnt);
end;
function find_kth(x,k:longint):longint;
var
ls,rs:longint;
begin
while true do
begin
ls:=t[x].ch[-1]; rs:=t[x].ch[1];
if k<=t[ls].size then x:=ls else
if k>t[ls].size+t[x].cnt then
begin
k:=k-t[ls].size-t[x].cnt;
x:=rs;
end else exit(t[x].key);
end;
end;
function find_pred(x,key:longint):longint;
begin
if x=0 then exit(-maxlongint);
if t[x].key>=key then exit(find_pred(t[x].ch[-1],key));
exit(max(find_pred(t[x].ch[1],key),t[x].key));
end;
function find_succ(x,key:longint):longint;
begin
if x=0 then exit(maxlongint);
if t[x].key<=key then exit(find_succ(t[x].ch[1],key));
exit(min(find_succ(t[x].ch[-1],key),t[x].key));
end;
begin
read(n); r:=1; t[0].fix:=maxlongint;
while n>0 do
begin
readln(flag,x);
if flag=1 then
if cnt=0 then
begin
inc(cnt);
t[0].ch[1]:=1; t[1].father:=0; t[1].size:=1; t[1].cnt:=1; t[1].key:=x; t[1].fix:=rand;
end else insert(0,0,x);
if flag=2 then delet(0,x);
if flag=3 then writeln(rank(0,x));
if flag=4 then writeln(find_kth(0,x));
if flag=5 then writeln(find_pred(0,x));
if flag=6 then writeln(find_succ(0,x));
dec(n);
end;
end.