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
例图:
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\)的关系的情况:
Z --> D; Z --> Y;
Y --> X; Y --> C;
X --> A; X --> B;
z --> d; z --> x;
	x --> a; x --> y;
	y --> b; y --> c;
Z --> Y; Z --> D;
Y --> C; Y --> X;
X --> A; X --> B;
z --> x; z --> d;
	x --> y; x --> b;
	y --> c; y --> a;
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的基本操作(插入/删除,查询)的更多相关文章
- 洛谷 P2042 [NOI2005]维护数列-Splay(插入 删除 修改 翻转 求和 最大的子序列)
		因为要讲座,随便写一下,等讲完有时间好好写一篇splay的博客. 先直接上题目然后贴代码,具体讲解都写代码里了. 参考的博客等的链接都贴代码里了,有空再好好写. P2042 [NOI2005]维护数列 ... 
- Hibernate插入、查询、删除操作 HQL
		Hibernate的所有的操作都是通过Session完成的. 基本步骤如下: 1:通过配置文件得到SessionFactory: SessionFactory sessionFactory=new C ... 
- Hibernate框架的基本搭建(一个小的java project的测试向数据库中插入和查询数据的功能)
		Hibernate介绍:Hibernate是一种“对象-关系型数据映射组件”,它使用映射文件将对象(object)与关系型数据(Relational)相关联,在Hibernate中映射文件通常以&qu ... 
- python Trie树和双数组TRIE树的实现. 拥有3个功能:插入,删除,给前缀智能找到所有能匹配的单词
		#coding=utf- #字典嵌套牛逼,别人写的,这样每一层非常多的东西,搜索就快了,树高26.所以整体搜索一个不关多大的单词表 #还是O(). ''' Python 字典 setdefault() ... 
- [LeetCode] Insert Delete GetRandom O(1) - Duplicates allowed 常数时间内插入删除和获得随机数 - 允许重复
		Design a data structure that supports all following operations in average O(1) time. Note: Duplicate ... 
- [LeetCode] Insert Delete GetRandom O(1) 常数时间内插入删除和获得随机数
		Design a data structure that supports all following operations in average O(1) time. insert(val): In ... 
- Trie树的创建、插入、查询的实现
		原文:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=28977986&id=3807947 1.什么是Trie树 Tr ... 
- java序列化对象 插入、查询、更新到数据库
		java序列化对象 插入.查询.更新到数据库 : 实现代码例如以下: import java.io.ByteArrayInputStream; import java.io.ByteArrayOutp ... 
- EF框架操作postgresql,实现WKT类型坐标的插入,查询,以及判断是否相交
		1.组件配置 首先,要下载.NET for Postgresql的驱动,npgsql,EF6,以及EntityFramework6.Npgsql,版本号 3.1.1.0. 由于是mvc项目,所以,把相 ... 
随机推荐
- for循环居然还可以这样写
			公司代码有点坑,查找问题,发现for循环的写法不是固定条件在中间,写反了也是可以运行的.比如:下面一个简单的for循环 int m=0; for(int i=0;i>3;i++){ m=m+i; ... 
- apache poi操作office文档----java在线预览txt、word、ppt、execel,pdf代码
			在页面上显示各种文档中的内容.在servlet中的逻辑 word: BufferedInputStream bis = null; URL url = null; HttpURLConnectio ... 
- python练习题:利用切片操作,实现一个trim()函数,去除字符串首尾的空格,注意不要调用str的strip()方法
			方法一: # -*- coding: utf-8 -*- # 利用切片操作,实现一个trim()函数,去除字符串首尾的空格,注意不要调用str的strip()方法: def trim(s): whil ... 
- C# Socket服务器及多客户端连接示例
			服务端代码[控制台示例] static List<Socket> Sockets = new List<Socket>(); static void Main(string[] ... 
- 如何简单使用tensorboard展示(一)
			我使用tensorboard中的graph做了展示,至于其它功能可以类推,其代码如下: import numpy as npimport tensorflow as tf x_img = np.arr ... 
- 微服务架构 ------ DockerCompose从安装到项目部署
			DockerCompose的目的:简化Docker的启动和停止流程,以及编排Docker启动服务与服务之间的关系 DockerCompose的安装:curl -L https://get.daoclo ... 
- C#实现outlook自动签名
			Outlook下实现自动签名的方式 网上找到一篇资料是在outlook里用vba实现的,但是这样实现的方式由于数字认证的问题不便于部署 在此介绍一种C#下实现的方式,目前确定的outlook版本为 ... 
- SpringMVC拦截器执行流程
			1:MyInterceptor1.MyInterceptor2这2个拦截器都放行 MyInterceptor1......preHandleMyInterceptor2......preHandle ... 
- 有些CRM settype用事务码COMM_ATTRSET打不开的原因
			This question is asked by Dr. Lin. Issue For example, settype COM_COMMERCIAL could be opened via tco ... 
- SAP里SE38设置模板
			经验丰富些的大佬们都会有一套自己的风格,比如report主程序里几个form,常见的fieldcat的宏定义,常见的一些数据定义等等. 1.使用事物代码SE38进入编辑器. 2.点击客户端最右下角的文 ... 
