算法学习:伸展树(splay)

【定义】

【平衡树】 每个叶子结点的深度差不超过1的二叉树

【伸展树】

【常用问题】

splay的操作,通过左旋右旋,将某个结点通过旋转旋转至根节点,使树的结构发生变化,尽可能的平衡
并且因为左旋右旋的性质,当原树是一个二叉排序树的时候,splay依旧能够使原树保持二叉排序树的性质

左旋右旋图片

【模板题】

【luogu P3369】普通平衡树
【题意】实现一颗二叉排序树的增删查改
【注】对数据结构的理解见注释

【代码】

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;

const int MAXN = 100010;
const int INF = 10000000;

class Splay
{
#define root e[0].ch[1]
private:
    class node
    {
    public:
        int v, father;
        int ch[2];
        int sum;//子树结点个数+自己的结点个数
        int recy;//纪录自己被重复多少次
    };
    node e[MAXN];
    int n, points;
public:
    void update(int x)//访问左右子树,更新树上所储存的数据
    {
        e[x].sum = e[e[x].ch[0]].sum + e[e[x].ch[1]].sum + e[x].recy;
    }
    int identify(int x)
    {
        return e[e[x].father].ch[0] == x ? 0 : 1;
        //自己是不是左孩子
    }
    void connect(int x, int f, int son)
    {
        e[x].father = f;
        e[f].ch[son] = x;
        //将结点x作为结点f的孩子
        return;
    }
    void rotate(int x)
    {
        //旋转操作的语言描述
                //将被指定的节点向上移动一级,并将原有的父级节点作为自己的儿子
        //把自己的儿子的位置给自己的爸爸当新儿子
        //把自己爸爸放在自己原来的位置

        int y = e[x].father;
        //自己爸爸
        int mroot = e[y].father;
        //爸爸的爸爸
        int mrootson = identify(y);
        //自己爸爸对于爷爷的位置
        int yson = identify(x);
        //自己对于爸爸的位置
        int B = e[x].ch[yson ^ 1];
        //自己需要爸爸继承的自己的儿子
        connect(B, y, yson);
        //把自己需要爸爸带的儿子先给爸爸
        connect(y, x, (yson ^ 1));
        //把爸爸给自己已经把儿子给爸爸之后空出来的位置给爸爸
        connect(x, mroot, mrootson);
        //把自己给爷爷
        update(y), update(x);
    }
    void splay(int at, int to)
    {
        to = e[to].father;
        while (e[at].father != to)
        {
            int up = e[at].father;
            if (e[up].father == to) rotate(at);
            else if (identify(up) == identify(at))
            {
                rotate(up);
                rotate(at);
            }
            else
            {
                rotate(at);
                rotate(at);
            }
        }
    }
    int crepoint(int v, int father)
    {
        n++;
        e[n].v = v;
        e[n].father = father;
        e[n].sum = e[n].recy = 1;
        return n;
    }
    void destroy(int x)
    {
        e[x].v = e[x].ch[0] = e[x].ch[1] = e[x].sum = e[x].father = e[x].recy = 0;
        if (x == n) n--;
    }
    int find(int v)
    {
        int now = root;
        while (true)
        {
            if (e[now].v == v)
            {
                splay(now, root);
                return now;
            }
            int next = v < e[now].v ? 0 : 1;
            if (!e[now].ch[next])   return 0;
            now = e[now].ch[next];
        }
    }
    int build(int v)
    {
        points++;
        if (n == 0)
        {
            root = 1;
            crepoint(v, 0);
        }
        else
        {
            int now = root;
            while (true)
            {
                e[now].sum++;
                if (v == e[now].v)
                {
                    e[now].recy++;
                    return now;
                }
                int next = v < e[now].v ? 0 : 1;
                if (!e[now].ch[next])
                {
                    crepoint(v, now);
                    e[now].ch[next] = n;
                    return n;
                }
                now = e[now].ch[next];
            }
        }
        return 0;
    }
    void push(int v)
    {
        int add = build(v);
        splay(add, root);
    }
    void pop(int v)//删除结点
    {
        int deal = find(v);
        //找到结点
        if (!deal) return;
        points--;
        if (e[deal].recy > 1)
        {
            e[deal].recy--;
            e[deal].sum--;
            return;
        }
        //直接删除
        //去掉这个点
        if (!e[deal].ch[0])
        {
            root = e[deal].ch[1];
            e[root].father = 0;
        }
        else
        {
            int lef = e[deal].ch[0];
            //lef,他的左儿子
            while (e[lef].ch[1])
                lef = e[lef].ch[1];
            //找到他最右的结点,也就是这颗树上最小的值
            splay(lef, e[deal].ch[0]);
            //将这棵树旋到左结点
            int rig = e[deal].ch[1];
            connect(rig, lef, 1); connect(lef, 0, 1);
            update(lef);
        }
        destroy(deal);
    }
    int rank(int v)
    {
        int ans = 0, now = root;
        while (true)
        {
            if (e[now].v == v)
                {
                    ans = ans + e[e[now].ch[0]].sum + 1;
                    if (now) splay(now, root);
                    return ans;
                }
            if (now == 0)   return 0;
            if (v < e[now].v) now = e[now].ch[0];
            else
            {
                ans = ans + e[e[now].ch[0]].sum + e[now].recy;
                now = e[now].ch[1];
            }

        }

        return 0;
    }
    int atrank(int x)
    {
        if (x > points) return -INF;
        int now = root;
        while (true)
        {
            int minused = e[now].sum - e[e[now].ch[1]].sum;
            //左子树的个数
            if (x > e[e[now].ch[0]].sum && x <= minused) break;
            //如果这个数在这个范围内
            if (x < minused) now = e[now].ch[0];
            //如果小于,说明这个数在左子树中
            else
            {
                x = x - minused;
                now = e[now].ch[1];
            }
            //同上
        }
        splay(now, root);
        return  e[now].v;
    }
    int upper(int v)
    {
        int now = root;
        int result = INF;
        while (now)
        {
            if (e[now].v > v && e[now].v < result)  result = e[now].v;
            if (v < e[now].v)
                now = e[now].ch[0];
            else
                now = e[now].ch[1];
        }
        return result;
    }

    int lower(int v)
    {
        int now = root;
        int result = -INF;
        while (now)
        {
            if (e[now].v < v && e[now].v > result)  result = e[now].v;
            if (v > e[now].v)
                now = e[now].ch[1];
            else
                now = e[now].ch[0];
        }
        return result;
    }
#undef root
};
Splay T;
int main()
{
    int n;
    scanf("%d", &n);
    T.push(INF);
    T.push(-INF);
    while (n--)
    {
        int p, v;
        scanf("%d%d", &p, &v);
        switch (p)
        {
        case 1:
            T.push(v); break;
        case 2:
            T.pop(v); break;
        case 3:
            printf("%d\n", T.rank(v) - 1); break;
        case 4:
            printf("%d\n", T.atrank(v + 1)); break;
        case 5:
            printf("%d\n", T.lower(v)); break;
        case 6:
            printf("%d\n", T.upper(v)); break;
        default:
            break;
        }
    }
}

原文地址:https://www.cnblogs.com/rentu/p/12288918.html

时间: 2024-10-06 00:54:29

算法学习:伸展树(splay)的相关文章

树-伸展树(Splay Tree)

伸展树概念 伸展树(Splay Tree)是一种二叉排序树,它能在O(log n)内完成插入.查找和删除操作.它由Daniel Sleator和Robert Tarjan创造. (01) 伸展树属于二叉查找树,即它具有和二叉查找树一样的性质:假设x为树中的任意一个结点,x节点包含关键字key,节点x的key值记为key[x].如果y是x的左子树中的一个结点,则key[y] <= key[x]:如果y是x的右子树的一个结点,则key[y] >= key[x]. (02) 除了拥有二叉查找树的性质

算法学习 - 表达树的建立(后缀表达式法),树的先序遍历,中序遍历,后序遍历

表达树就是根据后缀表达式来建立一个二叉树. 这个二叉树的每个叶子节点就是数,真祖先都是操作符. 通过栈来建立的,所以这里也会有很多栈的操作. 树的先序遍历,中序遍历,后序遍历的概念我就不讲了,不会的自行百度,不然也看不懂我的代码. 下面是代码: // // main.cpp // expressionTree // // Created by Alps on 14-7-29. // Copyright (c) 2014年 chen. All rights reserved. // #includ

# 伸展树 Splay

伸展树 Splay 维基百科上称为伸展树,但是国内好像一般叫平衡树,是众多平衡树中比较优秀的一种. 平衡树左旋右旋不会影响中序遍历顺序. 一棵平衡树的中序遍历顺序是值递增排序的,相当于从小到大到大排了一次序. 平衡树的作用: 平衡树其实就是一棵二叉搜索树,set和map都是平衡树实现. 一棵二叉搜索树理论深度是\(O(log(n))\),但是当二叉树退化成链表的时候,深度就变成了\(O(n)\),很多\(O(log)\)级别从操作会退化成\(O(n)\)线性级别的操作.平衡树就是在不改变二叉搜索

伸展树(splay tree)

伸展树同样是一种平衡二叉搜索树,它的优势在于,在足够长的序列中能保证分摊意义上的高效率,同时也无需记录高度或者平衡因子等信息. 伸展树的高效前提是局部性:刚刚被访问到的数据,可能在短时间内被再次访问:将被访问的下一元素,可能就在不久之前刚刚访问过的元素的附近.因此,伸展树的策略,就是把刚刚访问到的节点,及时"伸展"到树根附近. 所谓"伸展"操作,其实就是BST中的旋转操作.如果每次经过适当旋转,将访问的节点提升一层,直到上升到树根,称为逐层伸展.可以验证,这种伸展方

【BBST 之伸展树 (Splay Tree)】

最近“hiho一下”出了平衡树专题,这周的Splay一直出现RE,应该删除操作指针没处理好,还没找出原因. 不过其他操作运行正常,尝试用它写了一道之前用set做的平衡树的题http://codeforces.com/problemset/problem/675/D,运行效果居然还挺好的,时间快了大概10%,内存少了大概30%. 1 #include <cstdio> 2 #include <cstring> 3 #include <string> 4 #include

浅谈伸展树(Splay)

//本文是一个暂时的小记,有不对的请大佬们指出~ 真正大佬的在这http://blog.csdn.net/clove_unique/article/details/50630280 伸展树(Splay Tree),也叫分裂树,是一种二叉排序树,它能在O(log n)内完成插入.查找和删除操作.它由丹尼尔·斯立特Daniel Sleator和罗伯特·恩卓·塔扬Robert Endre Tarjan在1985年发明的. 在伸展树上的一般操作都基于伸展操作:假设想要对一个二叉查找树执行一系列的查找操作

伸展树 Splay 模板

学习Splay的时候参考了很多不同的资料,然而参考资料太杂的后果就是模板调出来一直都有问题,尤其是最后发现网上找的各种资料均有不同程度的错误. 好在啃了几天之后终于算是啃下来了. Splay也算是平衡树的一种,但是跟AVL树.SBT不同的是,Splay并不是一直保持严格的平衡,因此在速度上可能要慢一些,但是统计学上仍能保证Splay具有O(logn)的均摊复杂度. Splay原生不需要附加任何空间,它的先天优势是其特有的Splay操作可以非常灵活地改变整棵树的结构形态,完成一般线段树.平衡树做不

POJ 3580 - SuperMemo - [伸展树splay]

题目链接:http://poj.org/problem?id=3580 Your friend, Jackson is invited to a TV show called SuperMemo in which the participant is told to play a memorizing game. At first, the host tells the participant a sequence of numbers, {A1, A2, ... An}. Then the h

模板——伸展树 splay 实现快速分裂合并的序列

伸展操作:将treap中特定的结点旋转到根 //将序列中从左数第k个元素伸展到根,注意结点键值保存的是原序列id void splay(Node* &o, int k) { int s = o->ch[0] == NULL ? 0 : o->ch[0]->s; int d = k <= s ? 0 : (k == s+1 ? -1 : 1); if(d == 1) k -= s+1; if(d != -1) { splay(o->ch[d], k); rotate(o

伸展树Splay

新学的,其实吧,就那么回事.... 看了几天,splay处理序列问题,真的非常厉害,翻转,插入,删除,线段树实现不了的功能,splay用起来很方便. POJ 3580 SuperMemo 这题基本就是检验模板的题,各种操作都有,错了好多次,发现以前写的代码有错了的,数据水点,给水过了,注意pushup. Splay模板 #include <cstdio> #include <cstring> #include <map> #include <algorithm&g