转载自:http://blog.csdn.net/ric_shooter/article/details/20636487
这是我的第一篇博文,由于被splay坑得太惨,所以毅然决定以此开博。
蜘蛛快来:伸展树
解释splay的文章满大街都是,但用pascal的毕竟少,所以这是用pascal代码来解释的(C++代码在最后)
知道BST的请自动跳到第6段
要学splay,首先要知道BST(二叉排序树)的概念
它或是一棵空树;或者是具有下列性质的二叉树:
(1)若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
(2)若右子树不空,则右子树上所有结点的值均大于它的根结点的值;
(3)左、右子树也分别为二叉排序树;
BST可以简单地用递归实现,下面是插入节点的操作:
-
procedure ins(p,k:longint);
-
begin
-
if p<a[k] then
-
if next[k].l=0 then
-
begin
-
inc(tot);
-
next[k].l:=tot;
-
a[tot]:=p;
-
end else
-
ins(p,next[k].l)else
-
if next[k].r=0 then
-
begin
-
inc(tot);
-
next[k].r:=tot;
-
a[tot]:=p;
-
end else
-
ins(p,next[k].r);
-
end;
不难看出,裸的BST很容易被卡,虽然期望复杂度是O(log n),但对付退化成一条链的数据就变成O(n).
所以,平衡树(BBST)应运而生
BBST有treap、splay、AVL、RBT、SBT等等
这里只讲splay。伸展树不像AVL,它不保证严格的平衡,但是编程复杂度大大降低,效率有点似乎萎。但功能很强大,几乎能实现其他平衡树的一切功能,是性价比很高的东西。
基本概念1:旋转
①ZIG与ZAG
若B是根节点左节点,则对其ZIG,把B拎起来,拉到根的位置,让A变成B的孩子,发现B有3个孩子了,就把E作为A的左儿子
显然,这样不会违反BST的性质,ZAG就是ZIG的反演。
②Zig-Zig与Zag-Zag
若节点x的父节点y不是根节点,且x与y同时是各自父节点的左孩子,则进行ZIG-ZIG
若都是右孩子,则进行ZAG-ZAG
Zig-Zig:先Zig【y】节点,再Zig【x】节点
Zag-Zag:先Zag【y】节点,再Zag【x】节点
建议读者自己画一画,理解一下。
③Zig-Zag 与Zag-Zig
若节点x的父节点y不是根节点,x与y中一个是其父节点的左孩子而另一个是其父节点的右孩子,则进行此操作。
这里的Zig和Zag与Zig-Zig及Zag-Zag里的不同,这里的旋转都是对【x】节点进行的。
有人读到这里可能会问,为什么要定义先旋转y,再旋转x的双旋呢?,都只对x进行旋转操作不是更好么?而且都只对x旋转对其他节点相对高度改变小,不正符合了splay把常访问节点提上来的初衷么?我也有这样的疑问,但是经过无数数据的测试,定义如是双旋比只对x旋转优了50%,这个Tarjan会证,我不会……
基本概念②:伸展(splay)
这个概念容易理解,就是对每次被查找、插入等操作的节点用上述方法旋转到根的位置。
代码:定义
father数组——》存储该节点的父节点
son数组——》son[x,1]表示x节点的左儿子,son[x,2]表示x节点的右儿子
Data数组——》存储节点的值
value数组——》存储该节点的值出现了几次
count数组——》count[x]表示以x为根的子树结点数量
其实用记录类型写会更漂亮和方便,但是这样存储有些地方可以压缩代码,虽然大多数人不喜欢,调试也麻烦,然而为了培养读者自己写代码的能力……(好吧其实是我不想改了)
Code:旋转操作
-
procedure Rotate(x,w:longint);inline;//x是要旋转的节点,w=1左旋,w=2右旋
-
var y:longint;
-
begin
-
y:=father[x];
-
count[y]:=count[y]-count[x]+count[son[x,w]];
-
count[x]:=count[x]-count[son[x,w]]+count[y];
-
son[y,3-w]:=son[x,w];//若右旋,将其父节点的左儿子设置为当前节点的右儿子,相反就……
-
if son[x,w]<>0 then father[son[x,w]]:=y;//设置当前节点儿子的父节点,相反也是……
-
father[x]:=father[y];
-
if father[y]<>0 then
-
if y=son[father[y],1] then son[father[y],1]:=x else son[father[y],2]:=x;
-
//修改x与其旋转后父节点的关联
-
father[y]:=x;son[x,w]:=y;//设置旋转后x与y的关系
-
end;
伸展操作
-
procedure splay(x:longint);inline;//伸展操作无需多解释,细心即可
-
var y:longint;
-
begin
-
while father[x]<>0 do
-
begin
-
y:=father[x];
-
if father[y]=0 then
-
if x=son[y,1]then rotate(x,2)//ZIG
-
else rotate(x,1)//ZAG
-
else
-
if y=son[father[y],1] then
-
if x=son[y,1]
-
then begin rotate(y,2);rotate(x,2)end//ZIG-ZIG
-
else begin rotate(x,1);rotate(x,2)end//ZAG-ZIG
-
else
-
if x=son[y,2]
-
then begin rotate(y,1);rotate(x,1)end//ZAG-ZAG
-
else begin rotate(x,2);rotate(x,1)end//ZIG-ZAG
-
end;
-
root:=x;//x成为根
-
end;
查找
-
function search(x,w:longint):longint;inline;//在以x为根的子树中,w为要查询的数,返回节点编号
-
begin
-
while data[x]<>w do
-
begin
-
if w=data[x] then exit(x);//找到就退出
-
if w<data[x] then//这里操作与BST一样
-
begin
-
if son[x,1]=0 then break;
-
x:=son[x,1];
-
end else
-
begin
-
if son[x,2]=0 then break;
-
x:=son[x,2];
-
end
-
end;
-
exit(x);
-
end;
插入
-
procedure insert(w:longint);inline;
-
var k,kk:longint;flag:boolean;
-
begin
-
if tot=0 then//tot记录当前树中的节点总数
-
begin
-
inc(tot);
-
father[1]:=0;count[1]:=1;data[1]:=w;root:=1;value[1]:=1;//root是根的编号
-
exit;
-
end;
-
k:=search(root,w);
-
if data[k]=w then//如果该数值已存在于树中,就只要……
-
begin
-
inc(value[k]);kk:=k;
-
flag:=true;
-
end else
-
begin//否则新建节点
-
inc(tot);
-
data[tot]:=w;father[tot]:=k;count[tot]:=1;value[tot]:=1;
-
if data[k]>w then son[k,1]:=tot else son[k,2]:=tot;
-
flag:=false;
-
end;
-
while k<>0 do
-
begin
-
inc(count[k]);//更新count值,自己yy一下
-
k:=father[k];
-
end;
-
if flag then splay(kk)else splay(tot);//flag决定伸展哪个节点
-
end;
求极值(类似于查找,自己yy即可)
-
function Extreme(x,w:longint):longint;inline;//x是要访问子树的根,w=1求max,w=2求min
-
const lala:array[1..2]of longint=(maxlongint,-maxlongint);
-
var k:longint;
-
begin
-
k:=search(x,lala[w]);
-
Extreme:=data[k];
-
splay(k);
-
end;
删除(核心思想即伸展欲删节点,合并左右子树)
-
procedure delete(x:longint);inline;//x是要删除的【数值】
-
var k,y:longint;
-
begin
-
k:=search(root,x);
-
if data[k]<>x then splay(k)//如果此数不在树中,伸展k
-
-
else
-
begin
-
splay(k);
-
-
if value[k]>1 then begin dec(value[k]);dec(count[k]);end else
-
if son[k,1]=0 then
-
begin
-
y:=son[k,2];
-
son[k,2]:=0;count[k]:=0;data[k]:=0;value[k]:=0;
-
root:=y;father[root]:=0;
-
end else
-
begin
-
father[son[k,1]]:=0;//切断左子树与根的关联
-
y:=Extreme(son[k,1],1);//左子树中的max
-
son[root,2]:=son[k,2];//左右子树合并
-
count[root]:=count[root]+count[son[k,2]];
-
if son[root,2]<>0 then father[son[root,2]]:=root;
-
data[k]:=0;son[k,1]:=0;son[k,2]:=0;value[k]:=0;
-
end
-
end
-
end;//有些赋为0的操作其实可以省略
求前驱后继
-
function pred(x:longint):longint;inline;//求前驱
-
var k:longint;
-
begin
-
k:=search(root,x);splay(k);
-
if data[k]<x then exit(data[k]);
-
exit(Extreme(son[k,1],1));
-
end;
-
function succ(x:longint):longint;inline;//求后继
-
var k:longint;
-
begin
-
k:=search(root,x);splay(k);
-
if data[k]>x then exit(data[k]);
-
exit(Extreme(son[k,2],2));
-
end;
求第k极值
-
function kth(x,w:longint):longint;inline;//w=1为求第x小值,w=2为求第x大值
-
var i:longint;
-
begin
-
i:=root;
-
while not((x>=count[son[i,w]]+1)and(x<=count[son[i,w]]+value[i]))and (i<>0)do
-
begin
-
if x>count[son[i,w]]+value[i] then
-
begin
-
x:=x-count[son[i,w]]-value[i];
-
i:=son[i,3-w];
-
end
-
else i:=son[i,w];
-
end;
-
kth:=i;
-
splay(i);
-
end;
求x是第几大
-
function findnum(x:longint):longint;inline;
-
var K:longint;
-
begin
-
k:=search(root,x);splay(k);
-
root:=k;
-
exit(count[son[k,1]]+1);
-
end;
我能想到的基本操作大概就这些,最后推荐一道模板题
这题的code就是把上面的过程函数拼起来就行。
然后下面是C++的完整代码
-
#include<cstdio>
-
#include<iostream>
-
#include <cstdlib>
-
-
using namespace std;
-
-
int n,root,i,tot,opt,x;
-
int father[100000],count[100000],data[100000],value[100000];
-
int son[100000][3];
-
-
inline void Rotate(int x,int w)
-
{
-
int y;
-
y=father[x];
-
count[y]=count[y]-count[x]+count[son[x][w]];
-
count[x]=count[x]-count[son[x][w]]+count[y];
-
son[y][3-w]=son[x][w];
-
if (son[x][w]) father[son[x][w]]=y;
-
father[x]=father[y];
-
if (father[y])
-
if (y==son[father[y]][1]) son[father[y]][1]=x;
-
else son[father[y]][2]=x;
-
father[y]=x;
-
son[x][w]=y;
-
}
-
-
inline void splay(int x)
-
{
-
int y;
-
while (father[x])
-
{
-
y=father[x];
-
if (!father[y])
-
if (x==son[y][1]) Rotate(x,2);
-
else Rotate(x,1);
-
else
-
if (y==son[father[y]][1])
-
if (x==son[y][1])
-
{
-
Rotate(y,2);Rotate(x,2);
-
}
-
else
-
{
-
Rotate(x,1);Rotate(x,2);
-
}
-
else
-
if (x==son[y][2])
-
{
-
Rotate(y,1);Rotate(x,1);
-
}
-
else
-
{
-
Rotate(x,2);Rotate(x,1);
-
}
-
}
-
root=x;
-
}
-
-
inline int search(int x,int w)
-
{
-
while (data[x]!=w)
-
{
-
if (w==data[x]) return x;
-
if (w<data[x])
-
{
-
if (!son[x][1]) break;
-
x=son[x][1];
-
}
-
else
-
{
-
if (son[x][2]==0) break;
-
x=son[x][2];
-
}
-
}
-
return x;
-
}
-
-
inline void insert(int w)
-
{
-
int k,kk;bool flag;
-
if (!tot)
-
{
-
tot=1;
-
father[1]=0;count[1]=1;data[1]=w;root=1;value[1]=1;
-
return;
-
}
-
k=search(root,w);
-
if (data[k]==w)
-
{
-
value[k]++;kk=k;
-
flag=true;
-
}
-
else
-
{
-
tot++;
-
data[tot]=w;
-
father[tot]=k;
-
count[tot]=1;
-
value[tot]=1;
-
if (data[k]>w) son[k][1]=tot;else son[k][2]=tot;
-
flag=0;
-
}
-
while (k)
-
{
-
count[k]++;
-
k=father[k];
-
}
-
if (flag) splay(kk);else splay(tot);
-
}
-
-
inline int Extreme(int x,int w)
-
{
-
const int lala[3]={0,2147483647,-2147483647};
-
int k,tmp;
-
k=search(x,lala[w]);
-
tmp=data[k];
-
splay(k);
-
return tmp;
-
}
-
-
inline void del(int x)
-
{
-
int k,y;
-
k=search(root,x);
-
if (data[k]!=x) splay(k);else
-
{
-
splay(k);
-
if (value[k]>1)
-
{
-
value[k]--;
-
count[k]--;
-
}
-
else
-
if (!son[k][1])
-
{
-
y=son[k][2];
-
son[k][2]=0;
-
count[k]=0;
-
data[k]=0;
-
value[k]=0;
-
root=y;
-
father[root]=0;
-
}
-
else
-
{
-
father[son[k][1]]=0;
-
y=Extreme(son[k][1],1);
-
son[root][2]=son[k][2];
-
count[root]=count[root]+count[son[k][2]];
-
if (son[root][2]!=0) father[son[root][2]]=root;
-
data[k]=0;son[k][1];son[k][2]=0;
-
value[k]=0;
-
}
-
}
-
}
-
-
inline int pred(int x)
-
{
-
int k;
-
k=search(root,x);
-
splay(k);
-
if (data[k]<x) return data[k];
-
return Extreme(son[k][1],1);
-
}
-
-
inline int succ(int x)
-
{
-
int k;
-
k=search(root,x);
-
splay(k);
-
if (data[k]>x) return data[k];
-
return Extreme(son[k][2],2);
-
-
}
-
-
inline int kth(int x,int w)
-
{
-
int i,tmp;
-
i=root;
-
while (!((x>=count[son[i][w]]+1)&&(x<=count[son[i][w]]+value[i]))&&(i!=0))
-
{
-
if (x>count[son[i][w]]+value[i])
-
{
-
x=x-count[son[i][w]]-value[i];
-
i=son[i][3-w];
-
}
-
else
-
i=son[i][w];
-
}
-
tmp=i;
-
splay(i);
-
return tmp;
-
}
-
-
inline int findnum(int x)
-
{
-
int k;
-
k=search(root,x);splay(k);
-
root=k;
-
return count[son[k][1]]+1;
-
}
-
-
int main()
-
{
-
scanf("%d",&n);
-
for(i=1;i<=n;i++)
-
{
-
if (i==3)
-
i=3;
-
scanf("%d%d",&opt,&x);
-
switch(opt)
-
{
-
case 1:insert(x);break;
-
case 2:del(x);break;
-
case 3:printf("%d\n",findnum(x));break;
-
case 4:printf("%d\n",data[kth(x,1)]);break;
-
case 5:printf("%d\n",pred(x));break;
-
case 6:printf("%d\n",succ(x));break;
-
default:break;
-
}
-
}
-
return 0;
-
}
~~~~~~~~~~~~~~感谢阅读~~~~~~~~~~~~~~