splay那点奇诡的东东(填坑ing)

来源:互联网 发布:知乎粉丝排名 编辑:程序博客网 时间:2024/06/04 14:42

  • 当然要在前面先哔一哔
  • splay
    • 没用的东西我们学来干嘛
    • 极其脑残的旋转
      • What is 旋转
      • 怎么旋转
        • left rotate
          • left rotate code
        • right rotate
          • right rotate code
      • 单旋
      • more scientific rotate
      • 双旋
        • situation one
        • situation two
        • situation three and four
        • splay code
    • splay的查找
      • find code
  • 例题
    • 例题1线段树最大值
      • problem
      • analysis
  • 为什么blog的末尾不哔一哔呢

当然要在前面先哔一哔

(首先,不知道BST是什么的话……戳这里)

众所周知BST的期望复杂度为O(log2n),但数据用卡直接就成了O(n)的复杂度
人类的智慧是伟大的,这时,BBT们就该出场了
BBT(平衡树),有red black tree、AVL、替罪羊、treap、splay
BBT采取了某些和谐的思想,把原本卡成链复杂度接近O(n)的BST,重新变回O(log2n)的复杂度

接下来,该介绍今天的主角——splay了


splay


没用的东西我们学来干嘛?

splay,中文名伸展树、分裂树,BBT的一种,实际是BST的神奇优化
(英文名叫splay或者是splay,我不知道,所以都称作splay了)

  • splay可以O(log2n)时间插入、查找以及删除,速度快到飞起,用途广,代码复杂度不高

  • 其实真正的情况是,所有能用线段树做的题目都能用splay切掉!

  • 比起其他BBT,splay不用记录某些东东,空间比其他BBT优

  • 仅是用旋转来让整课树不退化成链,这点和treap有点像

  • 但时间复杂度可是相当的不稳定……这个等会再解释

那就,不管那么多了


极其脑残的旋转


What is 旋转?

在放图片前说一下
splay旋转的目的是让一个节点通过旋转成为另一个节点的儿子节点,然后进行单点或区间操作
(最基础的两个旋转和treap的两个旋转是一样的,但是双旋……咳咳)


怎么旋转?


left rotate

左旋:把p的右儿子q旋转,使q成为p的父亲

左旋步骤:

  • 把q的父亲标记为p的父亲(即q的祖父)

  • 把p的父亲标记为q

  • 此时q有三个儿子节点,为了保证splay tree是二叉树,再把q的左儿子B的父亲标记为p

(不懂就画个图,清晰明了

由右图可知:A < P < B < Q < C,从右树转到左树,虽然节点位置发生了变化
A<P<B<Q<C 仍成立,BST性质没有被破坏


left rotate code
void left_rotate(int x){    downdata(father[x]);    downdata(x);    int y=father[x],z=father[y];    father[x]=z,father[y]=x;    if (z==0)root=x;    else    {        if (tree[z][1]==y)        {            tree[z][1]=x;        }        else tree[z][2]=x;    }    size[y]=size[tree[y][1]]+size[tree[x][1]]+1;    size[x]=size[x]+size[y]-size[tree[x][1]];    if (tree[x][1])    {        father[tree[x][1]]=y;    }    tree[y][2]=tree[x][1];    tree[x][1]=y;}

right rotate

右旋:把q的左儿子p旋转,使p成为q的父亲

步骤、性质和左旋同理,这里不再多讲


right rotate code
void right_rotate(int x){    downdata(father[x]);    downdata(x);    int y=father[x],z=father[y];    father[x]=z,father[y]=x;    if (z==0)root=x;    else    {        if (tree[z][1]==y)        {            tree[z][1]=x;        }        else tree[z][2]=x;    }    size[y]=size[tree[y][2]]+size[tree[x][2]]+1;    size[x]=size[x]+size[y]-size[tree[x][2]];    if (tree[x][2])    {        father[tree[x][2]]=y;    }    tree[y][1]=tree[x][2];    tree[x][2]=y;}

单旋

我们在这里,需要把z旋转到x的儿子节点(当x是z的祖父的时候)

直接右旋z


直接左旋z

单旋一共只有两种情况
(其实旋转很简单……自己心神意会一下)


more scientific rotate

简单不?简单,不过您以为这就完了?
OK经过一堆的单旋以后,splay树可能变成这样:

(可以自己思考思考为什么splay tree会变成这个狗(ノ=Д=)ノ┻━┻样)
那么,我们需要更加科学的方法


双旋

比如我们要把α这个节点旋转到节点β的儿子节点
如果现在α的父亲、祖父都不是β的话,我们就需要双旋

双旋的时间复杂度Tarjan证过均摊 O(log2n),而单旋是O(n),速度差别很大

(注意是均摊……均摊……均摊,不是期望!这就是splay时间复杂度不稳定的原因!)

双旋一共有四种情况


situation one

下图是把x旋到z以上的某个父亲节点

明显,先左旋y,再左旋x


situation two

下图是把z旋到x以上的某个父亲节点

同样的,先右旋y,再右旋z

上面两个是成一条的情况


situation three and four

下图是把x旋到z以上的某个父亲节点

这里很显然,先左旋x再右旋x

情况四和情况三是差不多的,即z的左儿子是y、y的右儿子是x,双旋是先右旋x再左旋x

图就不用再贴了


splay code

单旋叫做spaly,双旋叫做splay,貌似是这样?
哎算了不管那么多了 ̄3 ̄

反正我很确定的一点就是双旋的过程就被称作splay!

void splay(int x,int y){    if (x==y || x==0)return;    while (father[x]!=y)    {        if (father[father[x]]==y)        {            if (tree[father[x]][1]==x)            {                right_rotate(x);            }            else left_rotate(x);        }        else        {            if (tree[father[father[x]]][1]==father[x] && tree[father[x]][1]==x)            {                right_rotate(father[x]);                right_rotate(x);            }            else if (tree[father[father[x]]][1]==father[x] && tree[father[x]][2]==x)            {                left_rotate(x);                right_rotate(x);            }            else if (tree[father[father[x]]][2]==father[x] && tree[father[x]][1]==x)            {                right_rotate(x);                left_rotate(x);            }            else if (tree[father[father[x]]][2]==father[x] && tree[father[x]][2]==x)            {                left_rotate(father[x]);                left_rotate(x);            }        }    }}

splay的查找

splay的查找和普通BST的查找并没有什么区别
由于BST性质,比当前节点关键字小就往左边找,大就往右边找,刚刚好不就找到了嘛

恩设当前的节点为now,左儿子为nowleft,右儿子为nowright
size[x]表示x为根的树有多少个节点,那么明显now是第几小的数就是size[left]+1

可能说的有点反人类,我自己鼠绘了个图


find code

int find(int x,int y){    if (tree[x][1])downdata(tree[x][1]);    if (tree[x][2])downdata(tree[x][2]);    if (y==size[tree[x][1]]+1)return x;    else    {        if (y<size[tree[x][1]]+1)return query(tree[x][1],y);        else return query(tree[x][2],y-size[tree[x][1]]-1);    }}

例题

例题万岁


例题1:【线段树】最大值

problem

题目描述

在N(1<=N<=100000)个数A1…An组成的序列上进行M(1<=M<=100000)次操作,操作有两种:

(1)1 x y:表示修改A[x]为y;

(1)2 x y:询问x到y之间的最大值。

输入

第一行输入N(1<=N<=100000),表示序列的长度,接下来N行输入原始序列;接下来一行输入M(1<=M<=100000)表示操作的次数,接下来M行,每行为1
x y或2 x y

输出

对于每个操作(2)输出对应的答案。

样例输入

5 1 2 3 4 5 3 2 1 4 1 3 5 2 2 4

样例输出

4 5

数据范围限制

提示

【限制】

保证序列中的所有的数都在longint范围内


analysis

线段树裸题,但我要用splay把它切了!


为什么blog的末尾不哔一哔呢?