[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这样的神驼来说,完全不存在这样的问题。(强烈不建议初学者观看)
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=1; else
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 }
为了方便学习,将今天用到的参考列出来
[入门]
The Magical Splay (个人觉得最简单明了的一篇,有伪代码和很多优美的结论)
[进阶]
强大到无与伦比的数据结构splay-tree(含有大量的练习题的名字)
基本上以The Magical Splay为标准加上其他的几篇参考,一天之内拿下Splay是毫无压力的。
以下是今天的成果
2 begin
3 if son[0,tf[p]]=p then exit(0) else 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的常见维护手段。
——————————————————————————————————————
你说,我们的存在,永不消逝。对吧?
如果,我们都在努力创造了存在。我们,会幸福的。对吧?