您的位置:首页 > 理论基础 > 数据结构算法

splay的入门

2015-12-04 15:00 441 查看
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];//bug

f->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];//bug

f->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.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息