您的位置:首页 > 编程语言 > C语言/C++

splay详解(pascal&C++版)

2014-03-06 16:50 423 查看
        这是我的第一篇博文,由于被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;


我能想到的基本操作大概就这些,最后推荐一道模板题
in wikioi in BZOJ in
tyvj
这题的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;
}


~~~~~~~~~~~~~~感谢阅读~~~~~~~~~~~~~~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息