splay详解(pascal&C++版)

来源:互联网 发布:淘宝联盟刷引流人数 编辑:程序博客网 时间:2024/05/16 15:59
        这是我的第一篇博文,由于被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求minconst 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 endend;//有些赋为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);elseif (y==son[father[y]][1])if (x==son[y][1]){Rotate(y,2);Rotate(x,2);}else{Rotate(x,1);Rotate(x,2);}elseif (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]--;}elseif (!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];}elsei=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;}


~~~~~~~~~~~~~~感谢阅读~~~~~~~~~~~~~~

0 0
原创粉丝点击