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到根, 然后找左边最大的/右边最小的, 最后删除

Splay的基本操作(插入/删除,查询)的更多相关文章

  1. 洛谷 P2042 [NOI2005]维护数列-Splay(插入 删除 修改 翻转 求和 最大的子序列)

    因为要讲座,随便写一下,等讲完有时间好好写一篇splay的博客. 先直接上题目然后贴代码,具体讲解都写代码里了. 参考的博客等的链接都贴代码里了,有空再好好写. P2042 [NOI2005]维护数列 ...

  2. Hibernate插入、查询、删除操作 HQL

    Hibernate的所有的操作都是通过Session完成的. 基本步骤如下: 1:通过配置文件得到SessionFactory: SessionFactory sessionFactory=new C ...

  3. Hibernate框架的基本搭建(一个小的java project的测试向数据库中插入和查询数据的功能)

    Hibernate介绍:Hibernate是一种“对象-关系型数据映射组件”,它使用映射文件将对象(object)与关系型数据(Relational)相关联,在Hibernate中映射文件通常以&qu ...

  4. python Trie树和双数组TRIE树的实现. 拥有3个功能:插入,删除,给前缀智能找到所有能匹配的单词

    #coding=utf- #字典嵌套牛逼,别人写的,这样每一层非常多的东西,搜索就快了,树高26.所以整体搜索一个不关多大的单词表 #还是O(). ''' Python 字典 setdefault() ...

  5. [LeetCode] Insert Delete GetRandom O(1) - Duplicates allowed 常数时间内插入删除和获得随机数 - 允许重复

    Design a data structure that supports all following operations in average O(1) time. Note: Duplicate ...

  6. [LeetCode] Insert Delete GetRandom O(1) 常数时间内插入删除和获得随机数

    Design a data structure that supports all following operations in average O(1) time. insert(val): In ...

  7. Trie树的创建、插入、查询的实现

    原文:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=28977986&id=3807947 1.什么是Trie树 Tr ...

  8. java序列化对象 插入、查询、更新到数据库

    java序列化对象 插入.查询.更新到数据库 : 实现代码例如以下: import java.io.ByteArrayInputStream; import java.io.ByteArrayOutp ...

  9. EF框架操作postgresql,实现WKT类型坐标的插入,查询,以及判断是否相交

    1.组件配置 首先,要下载.NET for Postgresql的驱动,npgsql,EF6,以及EntityFramework6.Npgsql,版本号 3.1.1.0. 由于是mvc项目,所以,把相 ...

随机推荐

  1. pyspark 日常整理

    1  联表 df1.join(df2,连接条件,连接方式) 如:df1.join(df2,[df1.a==df2.a], "inner").show() 连接方式:字符串类型, 如 ...

  2. Angular6 学习笔记——路由详解

    angular6.x系列的学习笔记记录,仍在不断完善中,学习地址: https://www.angular.cn/guide/template-syntax http://www.ngfans.net ...

  3. linux内核debug的一种方式:procfs

    #include <linux/module.h> #include <linux/compat.h> #include <linux/types.h> #incl ...

  4. Redmine添加插件

    注意:插件的安装必须到Plugins对应的文件目录执行命令! Plugins Plugin list A full list of available Redmine plugins can be f ...

  5. Redis学习之zskiplist跳跃表源码分析

    跳跃表的定义 跳跃表是一种有序数据结构,它通过在每个结点中维持多个指向其他结点的指针,从而达到快速访问其他结点的目的 跳跃表的结构 关于跳跃表的学习请参考:https://www.jianshu.co ...

  6. java中的对象、类、包、模块、组件、容器、框架、架构的概念入门

    在Java中有那么一些概念:对象.类.包.模块.组件.容器.框架.这些概念都有一个共同的特点,就是[容纳]. 对象(Object) 在Java的世界里,对象是通过属性和方法来分别对应事务所具有的静态属 ...

  7. Appium+python自动化(七)- 初识琵琶女Appium(千呼万唤始出来,犹抱琵琶半遮面)- 上(超详解)

    简介 “千呼万唤始出来,犹抱琵琶半遮面”,经过前边的各项准备工作,终于才把appium这位琵琶女请出来.那么下边就由宏哥给各位看官.小伙伴们和童鞋们来引荐这位美女(帅哥).这一篇主要是对前边的内容做一 ...

  8. 【mybatis】mybatis一条sql更新两张数据表,mybatis关联更新多张数据表

    示例sql: <update id="receipt" parameterType="com.pisen.cloud.luna.ms.security.code.b ...

  9. EF Core反向导航属性解决多对一关系

    多对一是一种很常见的关系,例如:一个班级有一个学生集合属性,同时,班级有班长.语文课代表.数学课代表等单个学生属性,如果定义2个实体类,班级SchoolClass和学生Student,那么,班级Sch ...

  10. SQL Date 时间类型处理

    SQL 日期(Dates)   2019-10-17 22:17:26 当我们处理日期时,最难的任务恐怕是确保插入的日期的格式,与数据库中日期列的格式相匹配. 保存的如果是日期部分,查询不会有太大问题 ...