Splay讲解

来源:互联网 发布:淘宝怎样看自己的评价 编辑:程序博客网 时间:2024/05/17 08:22

伸展树(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
原创粉丝点击