Splay讲解

10 篇文章 0 订阅
3 篇文章 0 订阅

伸展树(Splay Tree)是AVL树不错的替代,它有以下几个特点:
(1)它是二叉查找树的改进,所以具有二叉查找树的有序性。
(2)对伸展树的操作的平摊复杂度是O(log2n)。
(3)伸展树的空间要求、编程难度非常低。

提到伸展树,就不得不提到AVL树和Read-Black树,虽然这两种树能够保证各种操作在最坏情况下都为logN,但是两都实现都比较复杂。而在实际情况中,90%的访问发生在10%的数据上。因此,我们可以重构树的结构,使得被经常访问的节点朝树根的方向移动。尽管这会引入额外的操作,但是经常被访问的节点被移动到了靠近根的位置,因此,对于这部分节点,我们可以很快的访问。这样,就能使得平摊复杂度为logN。

1、自底向上的伸展树
伸展操作Splay(x,S)是在保持伸展树有序性的前提下,通过一系列旋转操作将伸展树S中的元素x调整至树的根部的操作。
在旋转的过程中,要分三种情况分别处理:
(1)Zig 或 Zag
(2)Zig-Zig 或 Zag-Zag
(3)Zig-Zag 或 Zag-Zig
1.1、Zig或Zag操作
节点x的父节点y是根节点。

1.2、Zig-Zig或Zag-Zag操作
节点x的父节点y不是根节点,且x与y同时是各自父节点的左孩子或者同时是各自父节点的右孩子。


1.3、Zig-Zag或Zag-Zig操作
节点x的父节点y不是根节点,x与y中一个是其父节点的左孩子而另一个是其父节点的右孩子。

2.以上的操作我们就可以将某一个节点进行相应的旋转,接下来叙述一下如何对于区间进行操作。

首先我们要知道,伸展树是具有有序性的,无论进行如何反转操作,有序性都不会被破坏掉。这样子就对我们的区间操作提供了很大的方便,假设要对于区间【L,R】进行操作,我们先把L-1反转到根节点处,再把R+1节点反转到根的右孩子处,这样很显然的目前R节点的左孩子的子树就是我们要处理的这段区间,由于边界问题,我们经常加入两个虚拟节点,分别是数列的头部和尾部,然后就可以方便的进行操作了,把一棵子树剪切,翻转等操作都可以利用这个性质很好的实现。有时候为了边界问题,我们会添加两个虚节点,分别作为序列的开始和结尾。接下来是我的splay的实现代码,仅供参考。

#define N 500000
#define lc (tr[id].c[0])
#define rc (tr[id].c[1])

struct Tr {
    int fa, sum, val, c[2], lz;
}tr[N];

int newtr(int k, int f) {//新建立一个节点
    tr[tot].sum = 1, tr[tot].val =  k;
    tr[tot].c[0] = tr[tot].c[1] = -1;
    tr[tot].lz = 0;
    tr[tot].fa = f;
    return tot++;
}

void Push(int id) {
    int lsum, rsum;
    lsum = (lc == -1)?0:tr[lc].sum;
    rsum = (rc == -1)?0:tr[rc].sum;
    tr[id].sum = lsum+rsum+1;
}

void lazy(int id) {//懒操作
    if (tr[id].lz) {
        swap(lc, rc);
        tr[lc].lz ^= 1, tr[rc].lz ^= 1;
        tr[id].lz = 0;
    }
}

int build(int l, int r, int f) {//建树
    if (r < l) return-1;
    int mid = l+r>>1;
    int ro = newtr(data[mid], f);
    tr[ro].c[0] = build(l, mid-1, ro);
    tr[ro].c[1] = build(mid+1, r, ro);
    Push(ro);
    return ro;
}

void Rotate(int x, int k) {
    if (tr[x].fa == -1) return;
    int fa = tr[x].fa, w;
    lazy(fa), lazy(x);
    tr[fa].c[!k] = tr[x].c[k];
    if (tr[x].c[k] != -1) tr[tr[x].c[k]].fa = fa;
    tr[x].fa = tr[fa].fa, tr[x].c[k] = fa;
    if (tr[fa].fa != -1) {
        w = tr[tr[fa].fa].c[1]==fa;
        tr[tr[fa].fa].c[w] = x;
    }
    tr[fa].fa = x;
    Push(fa);
    Push(x);
}

void Splay(int x, int goal) {//将x节点转到goal的儿子上
    if (x == -1) return;
    lazy(x);
    while (tr[x].fa != goal) {
        int y = tr[x].fa;
        lazy(tr[y].fa), lazy(y), lazy(x);
        bool w = x==tr[y].c[1];
        if (tr[y].fa != goal && w == (y==tr[tr[y].fa].c[1]))
            Rotate(y, !w);
        Rotate(x, !w);
    }
    if (goal == -1) root = x;
    Push(x);
}

int find(int k) {//找到第k哥节点的ID
    int id = root;
    while (id != -1) {
        lazy(id);
        int lsum = (lc==-1)?0:tr[lc].sum;
        if (lsum >= k) {
            id = lc;
        }
        else if (lsum+1 == k) break;
        else {
            k = k-lsum-1;
            id = rc;
        }
    }
    return id;
}

int Index(int l, int r) {//将区间【l+1, r-1】化成一颗子树
    Splay(find(l),-1);
    Splay(find(r),root);
}

int Getnext(int id) {//寻找后继节点
    lazy(id);
    int p = tr[id].c[1];
    if (p == -1) return id;
    lazy(p);
    while (tr[p].c[0] != -1) {
        p = tr[p].c[0];
        lazy(p);
    }
    return p;
}

int del(int l, int r) {//将【l,r】切掉,返回切掉子树的根节点
    Index(l-1, r+1);
    int ro = KEY;
    tr[KEY].fa = -1;
    KEY = -1;
    Push(tr[root].c[1]);
    Push(root);
    return ro;
}

void cut(int k, int ro) {//将子树ro接到第k个树之后
    Index(k, k+1);
    KEY = ro;
    tr[ro].fa = tr[root].c[1];
    Push(tr[root].c[1]);
    Push(root);
}

void filp(int l, int r) {//对区间【l,r】反转
    Index(l-1, r+1);
    lazy(root), lazy(tr[root].c[1]);
    tr[KEY].lz ^= 1;
}

void Add(int l, int r, int d) {//区间【l,r】的数加上d
    Index(l-1, r+1);
    tr[KEY].add += d;
    tr[KEY].mi += d;
    tr[KEY].val += d;
    Push(tr[root].c[1]);
    Push(root);
}

void Delete(int x) {//删除第x个数
    Index(x-1, x+1);
    tr[KEY].fa = -1;
    tr[tr[root].c[1]].c[0] = -1;
    Push(tr[root].c[1]);
    Push(root);
}

void Insert(int l, int x) {//在l之后插入x
    Index(l, l+1);
    int ro;
    ro = newtr(x, tr[root].c[1]);
    KEY = ro;
    Push(tr[root].c[1]);
    Push(root);
}

void Revolve(int l, int r, int d) {//【l, r】整体右移d位
    int ro = del(r+1-d, r);
    cut(l-1, ro);
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值