转载自: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;
- 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;
-
- father[y]:=x;son[x,w]:=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)
- else rotate(x,1)
- else
- if y=son[father[y],1] then
- if x=son[y,1]
- then begin rotate(y,2);rotate(x,2)end
- else begin rotate(x,1);rotate(x,2)end
- else
- if x=son[y,2]
- then begin rotate(y,1);rotate(x,1)end
- else begin rotate(x,2);rotate(x,1)end
- end;
- root:=x;
- end;
查找
- function search(x,w:longint):longint;inline;
- begin
- while data[x]<>w do
- begin
- if w=data[x] then exit(x);
- if w<data[x] then
- 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
- begin
- inc(tot);
- father[1]:=0;count[1]:=1;data[1]:=w;root:=1;value[1]:=1;
- 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]);
- k:=father[k];
- end;
- if flag then splay(kk)else splay(tot);
- end;
求极值(类似于查找,自己yy即可)
- function Extreme(x,w:longint):longint;inline;
- 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;
- var k,y:longint;
- begin
- k:=search(root,x);
- if data[k]<>x then splay(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);
- 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;
求前驱后继
- 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;
- 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;
- }
~~~~~~~~~~~~~~感谢阅读~~~~~~~~~~~~~~