splay的入门

来源:互联网 发布:淘宝必须评价吗 编辑:程序博客网 时间:2024/04/29 02:53

splay玄学,神奇,多变,应用广。均摊时间复杂度O(n log n) (不会证明,好像都是这么说“可以证明”的,单次最坏情况是O(n),但是平均下来是n log n).

思路很简单,基于rotate操作,和splay把某个点旋到那个节点之下。

几乎所有的操作都需要splay.


花了好长时间研究怎么写更简便,之后总结出了属于自己的参考模板。

(功能不全,基于bzoj1588的)

传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=1588

开始尝试结构体数组版本,发现写起来手残啊,现改用指针版本,较为方便。


必备知识:splay的性质,左旋右旋,zigzag和zigzig.

splay也是一种平衡树,是相当于科学的红黑树的替代品(红黑树码量太长了),效率波动接近于treap(treap要快一点,是用堆维护的,但是应用有局限性(当然不是平衡树一类))


splay性质:稳定维护的树的中序遍历不变,且中序遍历对应原数组排列。每个节点的左子树的所有元素一定比这个节点元素小,右子树所有元素比其大(不考虑重复元素)。

左旋右旋:左右旋不改变这棵树的平衡性,将一个节点旋到它父节点的位置,且一般需要考虑3个节点(子,父,祖节点).

zigzag和zigzig:我们发现3个节点在一条线上的时候,把最下面的连续旋两次又会变成单链,这样下次访问的时候就很慢,为了保证效率,我们需要使它的高度尽可能小,因此,在不断的总结中我们发现,每次考虑连续两次旋转,可以降低整棵树高度。

不清楚思路的个人建议参考:http://blog.csdn.net/leolin_/article/details/6436037


目前必须掌握的是rotate和splay.

基于bzoj还需要插入和寻找前驱和后继。(根据树的平衡型,前把某个点splay到根,前驱就是左子树的最右节点,后继就是右子树的最左节点)。


初始化:

struct node{node *f;node *ch[2];int v;node(){f=ch[0]=ch[1]=NULL;v=0;}}S[maxn];node *root;
S[]数组是为这棵树静态申请的地址空间,每次新建节点就放一个位置。


rotate(双旋合一,理解的时候需要假设情况,假设左旋右旋)

void rotate(node *u){node *f=u->f;if(f==NULL)return ;int d=u==f->ch[1];node *ff=f->f;int dd=0;if(ff!=NULL)dd=f==ff->ch[1];//bugf->ch[d]=u->ch[d^1];if(u->ch[d^1]!=NULL)u->ch[d^1]->f=f;u->ch[d^1]=f;f->f=u;u->f=ff;if(ff!=NULL)ff->ch[dd]=u;}

splay: 每次考虑连续两次旋转(除了最后一次),下面这个是考虑把u选到p下面

void splay(node *u,node *p)//把u旋转到p的下面{while(u->f!=p){node *f=u->f;node *ff=f->f;if(ff==p){rotate(u);break;}int d=u==f->ch[1];int dd=f==ff->ch[1];if(d==dd)rotate(f);else rotate(u);rotate(u);}if(p==NULL)root=u;} 

插入:当插入一个元素的时候,我们需要找到合适的位置(空树特判),比当前节点小就看该节点的左儿子是否存在,存在继续找,不存在建立新节点;右边是一样的道理。

(切记,插入之后把新节点旋到根,不要问我为什么,这就是splay的玄学之处,不这样做在一些题中很可能超时)

void insert(int key){if(root==NULL)//空树{root=&S[++ncnt];root->v=key;return ;} node *x=root;node *y;while(1){//分向左找和向右找if(key<x->v) {if(x->ch[0])x=x->ch[0];else//插入 {y=&S[++ncnt];y->v=key;y->f=x;x->ch[0]=y;break;}}else{if(x->ch[1])x=x->ch[1];else{y=&S[++ncnt];y->v=key;y->f=x;x->ch[1]=y;break;}}}splay(y,NULL);}

前后继不多说了,相信不成问题。

如果放心不下,你可以遍历这棵树检查。


最后,附上AC代码:

#include<cstdio>#include<queue>#include<cstring>#include<cstdlib>#include<algorithm>#include<iostream>#include<cmath>#define maxn 100000+20 using namespace std;int n; int ncnt=0;int ans=0;struct node{node *f;node *ch[2];int v;node(){f=ch[0]=ch[1]=NULL;v=0;}}S[maxn];node *root;void rotate(node *u){node *f=u->f;if(f==NULL)return ;int d=u==f->ch[1];node *ff=f->f;int dd=0;if(ff!=NULL)dd=f==ff->ch[1];//bugf->ch[d]=u->ch[d^1];if(u->ch[d^1]!=NULL)u->ch[d^1]->f=f;u->ch[d^1]=f;f->f=u;u->f=ff;if(ff!=NULL)ff->ch[dd]=u;}void splay(node *u,node *p)//把u旋转到p的下面{while(u->f!=p){node *f=u->f;node *ff=f->f;if(ff==p){rotate(u);break;}int d=u==f->ch[1];int dd=f==ff->ch[1];if(d==dd)rotate(f);else rotate(u);rotate(u);}if(p==NULL)root=u;} //旋到根再访问 void insert(int key){if(root==NULL)//空树{root=&S[++ncnt];root->v=key;return ;} node *x=root;node *y;while(1){//分向左找和向右找if(key<x->v) {if(x->ch[0])x=x->ch[0];else//插入 {y=&S[++ncnt];y->v=key;y->f=x;x->ch[0]=y;break;}}else{if(x->ch[1])x=x->ch[1];else{y=&S[++ncnt];y->v=key;y->f=x;x->ch[1]=y;break;}}}splay(y,NULL);}int qian(node *u)//找u的前驱{node *x=u->ch[0];if(x==NULL)return 0x3f3f3f3f;while(x->ch[1]!=NULL)x=x->ch[1];return x->v;} int hou(node *u){node *x=u->ch[1];if(x==NULL)return 0x3f3f3f3f;while(x->ch[0]!=NULL)x=x->ch[0];return x->v;}void dfs(node *u){printf("%d ",u->v);if(u->ch[0])dfs(u->ch[0]);if(u->ch[1])dfs(u->ch[1]);}int main(){scanf("%d",&n);for(int i=1;i<=n;i++){int x;if(scanf("%d",&x)==EOF)x=0;insert(x);//旋到根int ll=qian(root);int rr=hou(root);if(ll==0x3f3f3f3f&&rr==0x3f3f3f3f)ans+=x;else ans+=min(abs(ll-x),abs(rr-x)); //if(i==5)dfs(root);}printf("%d\n",ans);return 0;}

本人不才,蒟蒻一枚,希望早日熟练掌握splay.

0 0
原创粉丝点击