splay的一些基本操作,保证一遍看懂!

本蒟蒻第一篇blog,写的不好请见谅!
所谓splay就是将一个节点旋转到顶点的操作,而splay这种数据结构就是为了保证树的平衡而做的

什么是树的平衡呢?
首先我们说的树是二叉搜索树,也就是大小分别是左中右从小到大(当然也可以为了需要变成右中左),并且满足整颗左子树全部小于根节点,右子树全部大于根节点。这种二叉搜索树的好处就在于每一次查询耗费的时间仅为层数,不用把整个数组都遍历一遍,而且从中删除数字也非常容易
二叉搜索树
但是缺点也是很明显的
这里写图片描述

所以我们引进了一种二叉搜索树的升级版——平衡树
平衡树是一棵二叉搜索树。它除了具有二叉搜索树的全部特征之外,还具有一个关键性的特征:“平衡”,即任意节点的左右子树高度差不超过1。
这个特性决定了它在面对特殊数据(例如那种专门卡普通二叉搜索树的数据)时,能够非常稳定的解决,只是牺牲了一些时间复杂度常数,但是基本不会被卡掉。

平衡树有非常多的实现方式,包括splay,treap(不是严格的平衡树),替罪羊树,红黑树,sbt(size balanced tree即严格平衡),AVL(后面三种本蒟蒻都不会)等
由于treap旋转丑陋,代码量较大,而且万一随机数被卡,那么得不偿失,所以考场上推荐使用我们的优美的——splay

splay的特点是非常稳定,但是常数巨大,因为不论什么操作每次都要进行splay

一个treap需要包含:val, rank(随机数),l, r
splay中包含:val, son[1] (左右孩子), fa(为了方便把父亲带上)

struct ppp{
    int son[2], fa, siz, val, num;
}s[300001];

num是一个小小的优化,比如遇到两个相同的数我们就不分开成两个点存储,而是存成一个点,然后把num标成2

为了能够处理那种链状的结构
所以这里我们引进两种

旋转:

(其实和treap的旋转式一样的)
这里写图片描述
这里我们称被旋转的节点是被提上去的点(如作图为对5旋转,右图为对8旋转)

旋转可以维持二叉搜索树的性质但是可以使树变得平衡

int get(int x){return (s[s[x].fa].son[1] == x);}//返回这一点是左儿子还是右儿子

void rotate(int x)
{
    int f = s[x].fa, ff = s[f].fa, which = get(x);
    s[f].son[which] = s[x].son[which ^ 1];
    if(s[f].son[which]) s[s[f].son[which]].fa = f;
    s[f].fa = x;
    s[x].son[which ^ 1] = f;
    s[x].fa = ff;
    if(ff)
        s[ff].son[s[ff].son[1] == f] = x;
    update(f); update(x);
}

treap完成这一操作是基于再加一个关键字rank维护rank的二叉堆性质来实现的

而splay就是通过判断两种情况来执行的

情况1:

一个点p和他的父亲同为一边的儿子(如图同为左儿子)
此时应先rotate(p.fa), 再rotate(p)
splayrotate

情况2:
一个点p和他的父亲不是同一边的儿子
此时两次rotate(p)即可

至于为什么要这样做才能最快……可以去看看Dr.Tarjan的论文

一直这样做直到要求的点被旋转到根节点
于是就有了史上最好看的splay代码

void splay(int x)
{
    for(int f; (f = s[x].fa); rotate(x))
        if(s[f].fa)
            rotate((get(f) == get(x)) ? f : x);
    root = x;
}

插入操作

其实很简单,就是一路寻找,如果小于那么就找左子树,否则找右子树,等于就直接num++

pre操作(即寻找一个点的前继)

这个很n简单,把该点rsplay到根节点,然后这棵树的左子树的一直取右节点便是前继了
这里写图片描述
同理可以做next操作(即求一个点的后继)

删除操作:

把一个点splay到根节点,然后直接移除,运用pre找到前继后直接移到根节点
这里写图片描述

最后附上完整的代码:

1829. [Tyvj 1728]普通平衡树

【题目描述】

您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
1. 插入x数
2. 删除x数(若有多个相同的数,因只删除一个)
3. 查询x数的排名(若有多个相同的数,因输出最小的排名)
4. 查询排名为x的数
5. 求x的前驱(前驱定义为小于x,且最大的数)
6. 求x的后继(后继定义为大于x,且最小的数)

【输入格式】

第一行为n,表示操作的个数,下面n行每行有两个数opt和x,opt表示操作的序号(1<=opt<=6)

【输出格式】

对于操作3,4,5,6每行输出一个数,表示对应答案

【样例输入】

10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598

【样例输出】

84185
492737
#include<cstdio>
#include<cstring>
#include<iostream>
#undef main
using namespace std;

inline int read()
{
    int x = 0, f = 1;char c = getchar();
    while(c > '9' || c < '0'){if(c == '-')  f = -1;c = getchar();}
    while(c >= '0' && c < '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
int n, m, cnt, root;
struct ppp{
    int son[2], fa, siz, val, num;
}s[300001];
void clear(int x){s[x].son[0] = s[x].son[1] = s[x].fa = s[x].siz = s[x].val = s[x].num = 0;}
void update(int x){s[x].siz = s[s[x].son[0]].siz + s[s[x].son[1]].siz + s[x].num;}
int get(int x){return (s[s[x].fa].son[1] == x);}//返回这一点是左儿子还是右儿子
//这里为了方便splay把zigzag打在一起了 
void rotate(int x)
{
    int f = s[x].fa, ff = s[f].fa, which = get(x);
    s[f].son[which] = s[x].son[which ^ 1];
    if(s[f].son[which]) s[s[f].son[which]].fa = f;
    s[f].fa = x;
    s[x].son[which ^ 1] = f;
    s[x].fa = ff;
    if(ff)
        s[ff].son[s[ff].son[1] == f] = x;
    update(f); update(x);
}
//史上最优美的splay 
void splay(int x)
{
    for(int f; (f = s[x].fa); rotate(x))
        if(s[f].fa)
            rotate((get(f) == get(x)) ? f : x);
    root = x;
}
void insert(int x, int pos)
{
    if(x == s[pos].val)
    {
        s[pos].num++;
        splay(pos);
        return ;    
    }
    if(x < s[pos].val)
    {
        if(!s[pos].son[0])
        {
            clear(++cnt);
            s[cnt].fa = pos, s[cnt].val = x, s[cnt].siz = 1, s[cnt].num = 1;
            if(pos) s[pos].son[0] = cnt;
            splay(cnt);
        }
        else
        insert(x, s[pos].son[0]);
    }
    else
    {
        if(!s[pos].son[1])
        {
            clear(++cnt);
            s[cnt].fa = pos, s[cnt].val = x, s[cnt].siz = 1, s[cnt].num = 1;
            if(pos) s[pos].son[1] = cnt;
            splay(cnt);
        }
        else
        insert(x, s[pos].son[1]);
    }
}
int getrank(int x, int pos)
{
    if(s[pos].val == x)
    {
        splay(pos);
        return s[s[pos].son[0]].siz + 1;
    }
    if(s[pos].val > x) return getrank(x, s[pos].son[0]);
    else return getrank(x, s[pos].son[1]); 
}
int getval(int x, int pos)
{
    if(x > s[s[pos].son[0]].siz && x <= s[s[pos].son[0]].siz + s[pos].num)
    {
        splay(pos);
        return s[pos].val; 
    }
    if(x <= s[s[pos].son[0]].siz)   return getval(x, s[pos].son[0]);
    else return getval(x - s[s[pos].son[0]].siz - s[pos].num, s[pos].son[1]);
}
int pre()
{
    int pos = s[root].son[0];
    while(s[pos].son[1])    pos = s[pos].son[1];
    return pos;
}
int nxt()
{
    int pos = s[root].son[1];
    while(s[pos].son[0])    pos = s[pos].son[0];
    return pos;
}
void del(int x)
{
    int kth = getrank(x, root);
    if(s[root].num > 1){s[root].num --; return;}
    if(!s[root].son[0] && !s[root].son[1]){clear(root);return;}
    if(!s[root].son[0])
    {
        root = s[root].son[1];
        clear(s[root].fa);
        s[root].fa = 0;
        return ;
    }
    if(!s[root].son[1])
    {
        root = s[root].son[0];
        clear(s[root].fa);
        s[root].fa = 0;
        return ;
    }
    int gen = root, left = pre();
    splay(left);
    s[root].son[1] = s[gen].son[1];
    s[s[gen].son[1]].fa = root;
    clear(gen), update(gen);
}

/*void dfs(int pos)
{
    if(!pos)    return;
    dfs(s[pos].son[0]);
    cout<<s[pos].val<<" ";
    dfs(s[pos].son[1]);
}*/
int main()
{
    int tmp, tmmp;
    scanf("%d", &n);
    while(n --)
    {
        scanf("%d%d", &tmp, &tmmp);
        if(tmp == 1)    insert(tmmp, root);
        if(tmp == 2)    del(tmmp);
        if(tmp == 3)    insert(tmmp, root), printf("%d\n", getrank(tmmp, root)), del(tmmp);
        if(tmp == 4)    printf("%d\n", getval(tmmp, root));
        if(tmp == 5)    insert(tmmp, root), printf("%d\n", s[pre()].val),del(tmmp);
        if(tmp == 6)    insert(tmmp, root), printf("%d\n", s[nxt()].val),del(tmmp);  
        //dfs(root), cout<<endl;
    }
    return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
我可以为你生成一段splay平衡树代码: // 插入代码 void SplayTree::insert(int key) { //如果根为空,则新节点作为根节点 if (root == NULL) { root = new SplayNode(key); return; } // 寻找节点 SplayNode *current = root, *parent = NULL; while (current != NULL) { parent = current; if (key < current->key) current = current->left; else if (key > current->key) current = current->right; else break; } // 如果key不存在,则插入 if (key != parent->key) { if (key < parent->key) parent->left = new SplayNode(key); else parent->right = new SplayNode(key); // 将新插入节点作为根节点 splay(parent); } } // 删除代码 void SplayTree::remove(int key) { SplayNode *node = search(key); if (node == NULL) return; // 如果没有孩子节点,则直接删除 if (node->left == NULL && node->right == NULL) { if (node->parent == NULL) { root = NULL; } else { if (node->parent->left == node) node->parent->left = NULL; else node->parent->right = NULL; } } // 如果有一个孩子节点,则将其孩子节点替换 else if (node->left == NULL || node->right == NULL) { SplayNode *child = (node->left)? node->left: node->right; if (node->parent == NULL) root = child; else { if (node->parent->left == node) node->parent->left = child; else node->parent->right = child; } } // 如果有两个孩子节点,则找到其后继节点,并将后继节点替换 else { SplayNode *successor = minimum(node->right); if (node->parent == NULL) root = successor; else { if (node->parent->left == node) node->parent->left = successor; else node->parent->right = successor; } successor->left = node->left; } delete(node); }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值