• Splay的基本操作(插入/删除,查询)


    Splay的基本操作(插入/删除,查询)

    概述

    • 这是一棵二叉查找树
    • 让频繁访问的节点尽量靠近根
      • 将查询,插入等操作的点"旋转"至根
    • 树的高度均摊为(log_n)

    变量

    int root, tot; // root为当前树根(与0相连), tot是最大的编号
    struct Snode
    {
        int ch[2], fa, val, cnt, size;
        /*
        ch[0], ch[1]分别为左右儿子
        fa是父亲节点, val是权值
        cnt是这个权值的个数,size是子树(含自己)的总元素个数
        */
    } T[MAXN];
    

    rotate

    例图:

    graph TD; Z --> Y; Z --> D; Y --> *X*; Y --> C; *X* --> A; *X* --> B; z --> *x*; z --> d; *x* --> a; *x* --> y; y --> b; y --> c;
    • 旋转(X) : 即把(X)放到父亲(Y)的位置,并且调整相关的(X,Y,Z)与儿子间的关系, 使之仍然满足二叉查找树的性质

    其余三种(X, Y, Z)的关系的情况:

    graph TD; Z --> D; Z --> Y; Y --> X; Y --> C; X --> A; X --> B; z --> d; z --> x; x --> a; x --> y; y --> b; y --> c;
    graph TD; Z --> Y; Z --> D; Y --> C; Y --> X; X --> A; X --> B; z --> x; z --> d; x --> y; x --> b; y --> c; y --> a;
    graph TD; Z --> D; Z --> Y; Y --> C; Y --> X; X --> A; X --> B; z --> d; z --> x; x --> y; x --> b; y --> c; y --> a;
    void rotate(int x)
    {
        int y = T[x].fa, z = T[y].fa;
        int zy = (T[z].ch[1] == y);
        int yx = (T[y].ch[1] == x);
        T[z].ch[zy] = x; T[x].fa = z;
        T[y].ch[yx] = T[x].ch[yx ^ 1]; T[T[x].ch[yx ^ 1]].fa = y;
        T[x].ch[yx ^ 1] = y; T[y].fa = x;
        update(y); update(x);
    }
    

    splay

    • 如果 "(X)和父亲(Y)的关系" 和 "(Y)和父亲(Z)的关系" 相等, 需要先rotate(y)rotate(x)​
    void splay(int x, int tar) // x --> tar
    {
        tar = T[tar].fa; // x --> tar's father's son
        while (T[x].fa != tar)
        {
            int y = T[x].fa, z = T[y].fa;
            if (z != tar)
                (T[z].ch[1] == y) ^ (T[y].ch[1] == x) ? rotate(x) : rotate(y);
            rotate(x);
        }
        if (tar == 0) root = x;
    }
    

    插入

    利用二叉查找树的性质,找到这个权值应该放的位置

    给从根下来的路径上size都+1

    如果之前没这个值,那么新建一个节点

    int newnode(int v, int fa)
    {
        int u = ++ tot;
        if (fa) T[fa].ch[v > T[fa].val] = u;
        T[u].fa = fa;
        T[u].val = v;
        T[u].ch[0] = T[u].ch[1] = 0;
        T[u].cnt = T[u].size = 1;
        return tot;
    }
    

    否则直接计数+1

    void insert(int v)
    {
        int now = root, fa = 0;
        if (root == 0) return (void) ( root = newnode(v, 0) );
        while (1)
        {
            T[now].size ++;
            if (T[now].val == v) return (void) ( T[now].cnt ++, splay(now, root) );
            if (!T[now].ch[v > T[now].val])
            {
                T[now].ch[v > T[now].val] = newnode(v, now);
                return (void) ( splay(T[now].ch[v > T[now].val], root) );
            }
            now = T[now].ch[v > T[now].val];
        }
    }
    

    注意插入完后,将其splay到根

    查找权值(v)的节点编号

    利用二叉查找树的性质

    int find(int v)
    {
        int now = root;
        if (!now) return 0;
        while (1)
        {
            if (T[now].val == v) { splay(now, root); return now; }
            if (!T[now].ch[v > T[now].val]) return 0;
            now = T[now].ch[v > T[now].val];
        }
    }
    

    查找完后,将其splay到根

    删除

    • 首先找到权值对应的点, splay到根

      int now = find(x);
      if (!now) return ;
      
    • 如果这个点有多个, 计数-1即可

      if (T[now].cnt > 1) return (void)(T[now].cnt --, T[now].size --);
      
    • 如果只剩这一个点, 清空即可

      if (!T[now].ch[0] && !T[now].ch[1]) return (void)(root = 0);
      
    • 如果有一个儿子, 将儿子变成根, 清空

      if (!T[now].ch[0]) return (void)(root = T[now].ch[1], T[root].fa = 0);
          if (!T[now].ch[1]) return (void)(root = T[now].ch[0], T[root].fa = 0);
      
    • 如果有两个儿子, 则将左子树中最大的变成根, 右子树变成现在的右儿子, 清空

      int left = T[root].ch[0];
          while (T[left].ch[1]) left = T[left].ch[1];
          splay(left, root);
          T[left].ch[1] = T[now].ch[1]; T[T[now].ch[1]].fa = root;
          clear(now);
          update(root);
      

    查找权值(v)的排名

    可以将这个值旋转到根,排名即左子树大小+1

    int rank(int v)
    {
        int now = find(v);
        if (!now) return 0;
        return T[T[root].ch[0]].size + 1;
    }
    

    或者二叉查找树做(查完splay)

    int rank(int v)
    {
        int ans = 0, now = root;
        while (1)
        {
            if (T[now].val == v)
            {
                ans +=  T[T[now].ch[0]].size + 1;
                splay(now, root);
                return ans;
            }
            if (now == 0) return 0;
            if (T[now].val > v) now = T[now].ch[0];
            else if (T[now].val < v)
            {
                ans += T[T[now].ch[0]].size + T[now].cnt;
                now = T[now].ch[1];
            }
        }
    }
    

    查找排名的权值

    还是二叉查找树, 查完splay

    int arank(int x)
    {
        if (x == 0) return 0;
        int now = root;
        while (1)
        {
            int used = T[now].cnt + T[T[now].ch[0]].size;
            if (used >= x && x > T[T[now].ch[0]].size) break;
            if (x > used) x -= used, now = T[now].ch[1];
            else now = T[now].ch[0];
        }
        splay(now, root);
        return T[now].val;
    }
    

    前驱/后继

    二叉查找树做

    int pre(int v)
    {
        int res = -INF, now = root;
        while (now)
        {
            if (v > T[now].val) res = max(res, T[now].val), now = T[now].ch[1];
            else now = T[now].ch[0];
        }
        return res;
    }
    int nex(int v)
    {
        int res = INF, now = root;
        while (now)
        {
            if (v < T[now].val) res = min(res, T[now].val), now = T[now].ch[0];
            else now = T[now].ch[1];
        }
        return res;
    }
    

    或者先插入, splay到根, 然后找左边最大的/右边最小的, 最后删除

  • 相关阅读:
    iOS -一些常用的方法
    handoff了解
    UIlabel
    扩展运行机制
    github -- fork提交项目
    iOS
    AppDelegate解析
    KVC
    KVO
    xcode升级后, 插件失效修复
  • 原文地址:https://www.cnblogs.com/Kuonji/p/10558301.html
Copyright © 2020-2023  润新知