[Algorithm]Splay(1)

今天好好研究了下传说中的Splay,如果真的比Treap强大的话那就将常用的平衡树换成Splay。

看了一上午别人的论文什么的,又在纸上反复的做了推理,下午尝试着将几个不同的Splay写法进行整理,晚上的时候用splay解决了几个问题。总体来讲,splay的确是一个非常强大的数据结构。当做平衡树只是它的用处之一,作为“伸展树”,它还能做些别人做不了的事情。

首先把今天的结论放在最前面:(只是个人的看法,因为对Splay了解不多,有错误请指出,万分感激)

1.splay的旋转操作与Treap是完全相同的(或者说二叉平衡树都是相同的), 唯一的不同就是Splay有其独特的操作“伸展”。这也就意味着Splay的代码量稍微大一些,速度也稍微慢一些,但是其功能比treap强大而且不需要维护额外的随机值。如果题目仅仅需要进行一下非常基本,如插入,查找,删除之类的基本平衡树操作。那treap应该还是首选,因为treap的维护实在是太简单了。不过一旦涉及到一下较为特殊的操作,那就只能用splay了。

2.另外一个经常与splay进行对比的就是线段树了,与treap和splay相反的是,好像在维护区间这方面,splay的要比线段树快上那么一些。splay的常数比线段树大太多了,能用线段树的时候不要用splay了,虽然splay的维护操作很神奇。但是同样的,因为线段树的结构和操作实在是太简单了,所以代码量要比splay少那么一个级别吧。

 综上所述,当遇到基本的操作时,treap和线段树因为其优秀的代码复杂度应该是首选,但是一旦出现比较复杂或是比较奇特的维护时,就应该用splay在强行乱搞来解决。我一下在想起来去年zbwmqlw神牛给我们讲课讨论的时候,碰到一道数据结构题目的时候,最常用的一句话就是:“呀,用splay硬搞一下就行了。”splay的功能真是强大的太过分了。不过,话说回来,上面说这么多,只是对我像我一样的普通人,对于像nehzilrz这样的神驼来说,完全不存在这样的问题。(强烈不建议初学者观看)

nehzilrz神牛的25行Splay 自下而上维护 还有一个30行自上而下维护的版本,见这里
 1 //第一个是自下而上的,25行(ps.这个是有双旋的……)
 2 
 3 struct node {
 4     int key,sz;
 5     bool is_root;
 6     #define lc c[0]
 7     #define rc c[1]
 8     node *c[2],*f;
 9     node();
10     void update(){ sz=lc->sz+rc->sz+1; }
11 }TNode[MAXN],*nil=TNode,*PN=TNode+1;
12 node::node(){ lc=rc=f=nil; sz=is_root=0; }
13 
14 void zig(node* y,bool w) {
15     node *x=y->f; y->f=x->f;
16     if (x->is_root) x->is_root=0, y->is_root=1else
17     if (x==x->f->lc) y=x->f->lc; else y=x->f->rc;
18     x->c[w]=y->c[!w]; x->c[w]->f=x; y->c[!w]=x; x->f=y;
19     x->update();
20 }
21 
22 void splay(node *x) {
23     while (!x->is_root) {
24         node *y=x->f; bool w=x==y->rc;
25         if (!y->is_root && w==(y==y->f->rc)) zig(y,w);
26         zig(x,w);
27     }
28     x->update();
29 }

为了方便学习,将今天用到的参考列出来

[入门]

 伸展树的基本操作与应用——杨思雨

 Splay_Tree

 The Magical Splay (个人觉得最简单明了的一篇,有伪代码和很多优美的结论)

[进阶]  

 BST拓展与伸展树(Splay)一日通

 运用伸展树解决数列维护问题

 强大到无与伦比的数据结构splay-tree(含有大量的练习题的名字)


基本上以The Magical Splay为标准加上其他的几篇参考,一天之内拿下Splay是毫无压力的。

以下是今天的成果

个人的Splay(PASCAL版)
 1 Function wh(p:Longint):Longint;//返回 p的是其父亲的左儿子(0)还是右儿子(1
 2   begin
 3     if son[0,tf[p]]=p then exit(0else exit(1);
 4   end;
 5 
 6 Procedure Rotate(p,w:Longint);//旋转 w代表方向 1=Zig 0=Zag
 7   var
 8     x,y:Longint;
 9   begin
10     x:=p;y:=tf[p];
11     son[wh(y),tf[y]]:=x;tf[x]:=tf[y];tf[y]:=x;
12     tf[son[w,x]]:=y;son[1-w,y]:=son[w,x];son[w,x]:=y;
13   end;
14 
15 procedure Splay(p,aim:Longint);//双旋的伸展操作 aim代表将p点伸展到根为aim时终止
16   begin
17     while not(tf[p]=aim) do
18       If tf[tf[p]]=aim then Rotate(p,1-wh(p)) else
19         begin
20           If wh(p) xor wh(tf[p]) = 0  then Rotate(tf[p],1-wh(tf[p])) else Rotate(p,1-wh(p));
21           Rotate(p,1-wh(p));
22         end;
23     if aim=0 then root:=p;
24   end;

然后的话,说几个应该知道的东西。

1.双旋的作用

  按照定义,伸展是要分六种情况讨论的,即zig,zag,zigzig,zagzag,zigzag,zagzig.(本质上,zig就是右旋,zag就是左旋)当然,zig和zag,zigzig和zagzag,zigzag和zagzig是对称的。所以可以写到一个函数里面去。但是我们都知道,对于平衡树来说,只选用单选就可以将结点搞到根上去。那我们为什么要蛋疼的写双旋呢,单旋的话,splay操作4、5行就搞定了。

  原因很简单,双旋更快。双旋比单旋要快那么30%到50%之间。(这是结论,具体请查看下面的参看1.1和1.2)

  具体造成这样的原因,个人觉得参看1.2说的应该是最靠谱的了。

  [参看]

     1.1单旋SPLAY的优化

     1.2Splay Tree

2.Splay的作者之一是Tarjan,对,你没想错,就是那个提出Goto有害论,LCA算法和强联通分量的Tarjan算法的那货.

3.Splay的一个重要进阶就是:动态树问题... 想到着我也只能膜拜当年能5分钟Splay5分钟动态树的QZ神牛了。

 

题目的话见下一篇吧,专门写Splay的常见维护手段。

posted @ 2012-03-25 23:21  PerSeAwe  阅读(1315)  评论(0编辑  收藏  举报