splay - tree 伸展树

来源:互联网 发布:自由基地军品服饰 淘宝 编辑:程序博客网 时间:2024/04/28 10:54

Splay是一种自平衡二叉树,它不需要刻意的去调整,要求也没有AVL树那么苛刻,代码量相对AVL树也短了不少,最重要的是它的操作非常灵活,可是基本实现线段树的功能.
在实际的应用中刚刚被访问过的数据极有可能会在很短的时间内再次被访问,所以我们只需要把刚刚查询过的节点旋转到树的顶端就行了,刚刚也提到了Splay是一种自平衡的二叉树,所谓的自平衡就是我们在旋转节点的时候把树的深度减少.要想达到减小深度的效果我们就需要一点小技巧了,我们不能一层一层的往上旋转,而是需要判断上面两层的状态,然后决定旋转方案.
我们先来看看Splay的两种旋转方式.
这里写图片描述 这里写图片描述
再看一张图,看看单旋转和双旋转的区别.
这里写图片描述
很容易看出 单旋转的情况下可能会被某些恶意的数据卡死.而双旋转的情况下经过把节点调整到根的过程中使得书的深度减少为原来的一半.
当然不是所有的情况都需要双旋转,当旋转方案是zig-zag或者zag-zig的时候如果我们继续用双旋转的方式先旋转祖父节点的话,那么旋转就会发生错误,因为通过第一次旋转之后如果目标节点是左孩子那么旋转后就变成了右孩子,如果目标节点是右孩子那么旋转之后就会变成左孩子.从而无法进行下一步的旋转.
先上结构体的定义

struct node{    bool mark;                  //是否需要旋转    int pre,sum,chd[2];         //父节点,以当前节点为根的结点数,左右孩子};
    辅助部分代码
inline int F(int rt){       //判断是那个孩子    return tr[tr[rt].pre].chd[1] == rt;}inline int push_up(int rt) //往上传递更新{    if(0 == rt)        return 0;    tr[tr[rt].chd[0]].pre = rt;    tr[tr[rt].chd[1]].pre = rt;    tr[rt].sum = tr[tr[rt].chd[0]].sum+tr[tr[rt].chd[1]].sum+1;    return 0;}inline int push_down(int rt)  //向下传递更新{    if(0 == rt)        return 0;    if(tr[rt].mark){        tr[rt].mark = false;        tr[tr[rt].chd[0]].mark ^= 1;        tr[tr[rt].chd[1]].mark ^= 1;        swap(tr[rt].chd[0],tr[rt].chd[1]);    }    return 0;}
    下面是判断旋转方式的代码
int splay(int rt,int rw){    int pre;    while(tr[rw].pre != rt)    {        pre=tr[rw].pre;        if(F(rw) != F(pre) || tr[pre].pre == rt)   //单旋转            Rotate(rw);        else{                                      //双旋转            Rotate(pre);            Rotate(rw);        }    }    if(rt)                                        //向上传递更新        push_up(rt);    return rw;}

旋转部分的代码

inline int Rotate(int rt){    int chd = F(rt);    int pre = tr[rt].pre;    tr[tr[rt].chd[!chd]].pre = pre;    tr[pre].chd[chd] = tr[rt].chd[!chd];    tr[rt].pre = tr[pre].pre;    tr[tr[pre].pre].chd[F(pre)] = rt;    tr[pre].pre= rt;    tr[rt].chd[!chd] = pre;    push_up(pre);     //更新    push_up(rt);       //更新    return 0;}

查询第x大的节点

int Find(int rt,int x){    push_down(rt);    if(tr[tr[rt].chd[0]].sum >= x)        return Find(tr[rt].chd[0],x);    else if(tr[tr[rt].chd[0]].sum+1 == x)        return rt;    else        return Find(tr[rt].chd[1],x-tr[tr[rt].chd[0]].sum-1);}

删除特定的节点只需要将节点旋转到叶节点就行,而删除一个区间[a,b]只需要插入a-1将a-1移动到根节点,插入b+1然后将b+1移动至a-1的下面,然后删除b+1的左子树就行了.

0 0
原创粉丝点击