splay详解(pascal&C++版)
2014-03-06 16:50
423 查看
这是我的第一篇博文,由于被splay坑得太惨,所以毅然决定以此开博。
蜘蛛快来:伸展树
解释splay的文章满大街都是,但用pascal的毕竟少,所以这是用pascal代码来解释的(C++代码在最后)
知道BST的请自动跳到第6段
要学splay,首先要知道BST(二叉排序树)的概念
它或是一棵空树;或者是具有下列性质的二叉树:
(1)若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
(2)若右子树不空,则右子树上所有结点的值均大于它的根结点的值;
(3)左、右子树也分别为二叉排序树;
BST可以简单地用递归实现,下面是插入节点的操作:
不难看出,裸的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:旋转操作
伸展操作
查找
插入
求极值(类似于查找,自己yy即可)
删除(核心思想即伸展欲删节点,合并左右子树)
求前驱后继
求第k极值
求x是第几大
我能想到的基本操作大概就这些,最后推荐一道模板题
in wikioi in BZOJ in
tyvj
这题的code就是把上面的过程函数拼起来就行。
然后下面是C++的完整代码
~~~~~~~~~~~~~~感谢阅读~~~~~~~~~~~~~~
蜘蛛快来:伸展树
解释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; }
~~~~~~~~~~~~~~感谢阅读~~~~~~~~~~~~~~
相关文章推荐
- splay详解(pascal&C++版)
- LeetCode - 119. Pascal's Triangle II - 思路详解 - C++
- 探索C++的秘密之详解extern "C"
- 探索C++的秘密之详解extern "C"
- 详解C++中指针(*)、取地址(&)、解引用(*)与引用(&)的区别 (完整代码)
- 探索C++的秘密之详解extern "C"
- 探索C++的秘密之详解extern "C"
- 探索C++的秘密之详解extern "C"
- C++ using namespace std 详解
- 详解C++中经常看到的#include <iostream> using namespace std;
- C++&Pascal&Python——【USACO 5.3.4】——Big Barn
- C & C++的编译过程详解
- 探索C++的秘密之一详解extern "C"
- 探索C++详解extern "C"
- C++&Pascal——【USACO 3.3.1】——Riding the Fences
- 详解让C++新手闹心的语句“cout<<"Hello!"<<endl;”
- C++ & Pascal——NOIP2016提高组day2 t3——愤怒的小鸟
- [C++]LeetCode: 8 Pascal's Triangle II
- C++的秘密之详解extern "C"
- 详解C++中的纯虚函数(虚函数区别)&多态性 以及理解