首页 > 其他分享 > 浅谈 Splay

浅谈 Splay

时间:2022-10-12 09:48:40浏览次数:82  
标签:rt tmp ch 浅谈 int Splay fa 节点

前言

想了想还是写吧。

前置芝士

旋转和 BST。

正文

现发表一下个人的看法,splay 其实就是一种优雅的暴力的 BST,通过把一个节点不断旋转到根来维护其 BST 的性质。

旋转

因为你更改或插入时要更改从本节点到根节点路径上所有点的大小,所以就需要用到旋转了,也就是把本节点旋转到根。

具体的,对于一个节点,如果其性质和他的父亲的性质相同,就旋转他的父亲,反之旋转他自己,最后旋转他自己。

重复以上步骤直到根节点即可。

对于一个节点的性质为其属于其父亲的左子树或右子树。

int get(int x) { return x==ch[fa[x]][1]; }

void rotate(int x) {
	int y=fa[x];
	int z=fa[y],tmp=get(x);
	ch[y][tmp]=ch[x][tmp^1];
	if (ch[x][tmp^1]) fa[ch[x][tmp^1]]=y;
	ch[x][tmp^1]=y;
	fa[y]=x;
	fa[x]=z;
	if (z) ch[z][y==ch[z][1]]=x;
	upd(y);
	upd(x);
}

void splay(int x) {
	for (int i=fa[x];i=fa[x],i;rotate(x))
		if (fa[i]) rotate(get(x)==get(i)?i:x);
	rt=x;
}

插入

没什么好说的,对于当前节点如果插入值比它大,往右子树搜,反之往左子树搜,再分几种情况:

  1. 根节点为空,建一个新节点作为根节点。

  2. 树中存在这个数,搜到此节点,更新,然后旋转到根。

  3. 树中不存在这个数,一直搜到叶子节点,建立一个属于他自己的新节点,更新,旋转到根。

void ins(int x) {
	if (!rt) {
		rt=++cnt;
		val[cnt]=x;
		num[cnt]++;
		upd(rt);
		return;
	}
	int y=rt,f=0;
	while (1) {
		if (x==val[y]) {
			num[y]++;
			upd(y);
			upd(f);
			splay(y);
			break;
		}
		f=y;
		y=ch[y][x>val[y]];
		if (!y) {
			val[++cnt]=x;
			num[cnt]++;
			fa[cnt]=f;
			ch[f][val[cnt]>val[f]]=cnt;
			upd(cnt);
			upd(f);
			splay(cnt);
			break;
		}
	}
}

查询排名

首先不保证它在树中,所以要先插入,最后删掉即可。

在树中搜索,同样的,对于当前节点如果插入值比它小,往左子树搜,反之让答案加上左子树的大小,如果当前节点的值是我们所求的,就返回答案+1并旋转到根,否则再加上当前节点的个数并往右子树搜。

int rk(int x) {
	int res=0,y=rt;
	while (1) {
		if (x<val[y]) {
			y=ch[y][0];
		} else {
			res+=siz[ch[y][0]];
			if (x==val[y]) {
				splay(y);
				return res+1;
			}
			res+=num[y];
			y=ch[y][1];
		}

	}
}

查询给定名次的值

在树中搜索,同样的,如果当前节点的左子树大小大于等于排名,往左子树搜,反之让排名减去左子树的大小和当前节点的个数,如果排名此时小于等于 \(0\),就返回当前节点并旋转到根,否则往右子树搜。

int kth(int x) {
	int y=rt;
	while (1) {
		if (ch[y][0] and x<=siz[ch[y][0]]) {
			y=ch[y][0];
		} else {
			x-=(siz[ch[y][0]]+num[y]);
			if (x<=0) {
				splay(y);
				return val[y];
			}
			y=ch[y][1];
		}
	}
}

前驱和后继

搜前驱就去找其左子树中的最大值,后继就是找其右子树中的最小值。

int pre(int x) {
	int y=ch[x][0];
	if (!y) return y;
	while (ch[y][1]) y=ch[y][1];
	splay(y);
	return y;
}

int nxt(int x) {
	int y=ch[x][1];
	if (!y) return y;
	while (ch[y][0]) y=ch[y][0];
	splay(y);
	return y;
}

删除

分五种情况:

  1. 当前节点个数大于 \(1\),个数减一,更新,旋转到根。

  2. 左右子树皆为空(即为根节点),删除本节点,rt=0

  3. 左子树有,右子树没有,删除本节点,rt=t[rt].ch[0]

  4. 左子树没有,右子树有,删除本节点,rt=t[rt].ch[1]

  5. 左右子树皆有,找到本节点的前驱,把前驱的右子树改为本节点的右子树,本节点的右子树的父亲改为前驱,删除本节点并更新。

void clear(int x) {siz[x]=ch[x][0]=ch[x][1]=fa[x]=num[x]=val[x]=0;}

void del(int x) {
	rk(x);
	if (num[rt]>1) {
		num[rt]--;
		upd(rt);
	} else if (!ch[rt][1] and !ch[rt][0]){
		clear(rt);
		rt=0;
	} else if (!ch[rt][1] and ch[rt][0]) {
		int tmp=rt;
		rt=ch[rt][0];
		fa[rt]=0;
		clear(tmp);
	} else if (ch[rt][1] and !ch[rt][0]) {
		int tmp=rt;
		rt=ch[rt][1];
		fa[rt]=0;
		clear(tmp);
	} else {
		int tmp=rt,x=pre(rt);
		fa[ch[tmp][1]]=x;
		ch[x][1]=ch[tmp][1];
		clear(tmp);
		upd(rt);
	}
}

更新

没啥好说的。

void upd(int x) { siz[x]=siz[ch[x][0]]+siz[ch[x][1]]+num[x]; }

习题

P3369 【模板】普通平衡树

把板子贴上去即可

P3391 【模板】文艺平衡树

就是把 splay 当区间树用,再加上一个 \(lazytag\),对于每一个区间 \([l,r]\),把 \(l-1\) 旋转到根,再把 \(r+1\) 旋转到根的右子树,再对 \(r+1\) 的左子树打上 \(lazytag\) 即可。

旋转部分代码:

void splay(int x,int k) {
	for (int f=t[x].fa;f=t[x].fa,f!=k;rotate(x)) 
		if (t[f].fa!=k) rotate(get(x)==get(f)?f:x);
	if (!k) rt=x;
}

P2596 [ZJOI2006]书架

把初始时每本书上面书的本数作为初始权值插入,并定义 \(loc_i\) 为第 \(i\) 本书在树中的编号。

  1. 对于操作 Top ,把本节点旋转到根,找到前驱,将右子树接到前驱的右子树出,更新旋转到根。

  2. 对于操作 Bottom ,同上。

  3. 对于操作 Insert,因为 \(t \in \{-1,0,1\}\),所以当 \(t\) 为 \(0\) 时,直接 continue,那么当 \(t=-1\) 时,找到前驱,交换权值和 \(loc\),否则反之。

  4. 对于操作 Ask,将 \(s\) 旋转到根,输出左子树大小。

  5. 对于操作 Query,就是求排名。

#include <iostream>
#include <string>
#include <cstdio>

#define INF 100000

using namespace std;
int n,m;

namespace Splay {
    struct Node {
        int val,num,siz,fa,ch[2];
    }t[1000001];

    int rt,cnt,loc[1000001];

    void upd(int x) { t[x].siz=t[t[x].ch[0]].siz+t[t[x].ch[1]].siz+t[x].num; }

    int get(int x) { return x==t[t[x].fa].ch[1]; }

    void rotate(int x) {
        int y=t[x].fa,z=t[y].fa,tmp=get(x);
        t[y].ch[tmp]=t[x].ch[tmp^1];
        if (t[x].ch[tmp^1]) t[t[x].ch[tmp^1]].fa=y;
        t[x].ch[tmp^1]=y;
        t[y].fa=x;
        t[x].fa=z;
        if (z) t[z].ch[y==t[z].ch[1]]=x;
        upd(y);
        upd(x);
    }

    void splay(int x,int goal) {
        for (int f=t[x].fa;f=t[x].fa,f!=goal;rotate(x))
            if (t[f].fa!=goal) rotate(get(f)==get(x)?f:x);
        loc[t[x].val]=x;
        if (!goal) rt=x;
    }

    int find_max() {
        int y=t[rt].ch[1];
        if (!y) return rt;
        while (t[y].ch[1]) y=t[y].ch[1];
        splay(y,0);
        return y;
    }

    int find_min() {
        int y=t[rt].ch[0];
        if (!y) return rt;
        while (t[y].ch[0]) y=t[y].ch[0];
        splay(y,0);
        return y;
    }

    int rk(int x) {
        splay(x,0);
        return t[t[x].ch[0]].siz;
    }

    int kth(int x) {
        int y=rt;
        while (1) {
            if (x<=t[t[y].ch[0]].siz) y=t[y].ch[0];
            else {
                x-=t[y].num+t[t[y].ch[0]].siz;
                if (x<=0) {
                    splay(y,0);
                    return y;
                }
                y=t[y].ch[1];
            }
        }
    }

    int pre(int x,int k) {
        int y=t[x].ch[k];
        if (!y) return y;
        while (t[y].ch[k^1]) y=t[y].ch[k^1];
        splay(y,0);
        return y;
    }

    void ins(int x) {
        if (!rt) {
            t[rt=++cnt].val=x;
            t[cnt].num++;
            loc[x]=cnt;
            upd(cnt);
            return;
        }
        find_max();
        t[++cnt].val=x;
        t[cnt].num++;
        t[cnt].fa=rt;
        loc[x]=cnt;
        t[rt].ch[1]=cnt;
        upd(cnt);
        upd(rt);
        splay(cnt,0);
    }
}
using namespace Splay;

void work(int x) {
    if (!x) return;
    work(t[x].ch[0]);
    printf("%d ",t[x].val);
    work(t[x].ch[1]);
}

void work1(int x) {
    if (!x) return;
    work1(t[x].ch[0]);
    printf("%d %d  ",loc[t[x].val],x);
    work1(t[x].ch[1]);
}

int main() {
    scanf("%d%d",&n,&m);
    for (int i=1,x;i<=n;i++) {
        scanf("%d",&x);
        ins(x);
    }
    string s;
    for (int i=1,x,tt;i<=m;i++) {
        cin>>s;
        scanf("%d",&x);
        if (s=="Top") {
            splay(loc[x],0);
            if (!t[rt].ch[0]) continue;
            if (!t[rt].ch[1]) {
                t[rt].ch[1]=t[rt].ch[0];
                t[rt].ch[0]=0;
                continue;
            }
            int y=t[rt].ch[1];
            while (t[y].ch[0]) y=t[y].ch[0];
            t[y].ch[0]=t[rt].ch[0];
            if (t[y].ch[0]) t[t[y].ch[0]].fa=y;
            t[rt].ch[0]=0;
            upd(y);
            splay(t[y].ch[0],0);
        } else if (s=="Bottom") {
            splay(loc[x],0);
            if (!t[rt].ch[1]) continue;
            if (!t[rt].ch[0]) {
                t[rt].ch[0]=t[rt].ch[1];
                t[rt].ch[1]=0;
                continue;
            }
            int y=t[rt].ch[0];
            while (t[y].ch[1]) y=t[y].ch[1];
            t[y].ch[1]=t[rt].ch[1];
            if (t[y].ch[1]) t[t[y].ch[1]].fa=y;
            t[rt].ch[1]=0;
            upd(y);
            splay(t[y].ch[1],0);
        } else if (s=="Insert") {
            splay(loc[x],0);
            scanf("%d",&tt);
            if (!tt) continue;
            int t1=pre(loc[x],(tt==-1?0:1));
            if (!t1) continue;
            swap(t[loc[x]].val,t[rt].val);
            swap(loc[x],loc[t[loc[x]].val]);
        } else if (s=="Ask") {
            printf("%d\n",rk(loc[x]));
        } else if (s=="Query") {
            printf("%d\n",t[kth(x)].val);
        }
        // work(rt);
        // printf("\n");
    }
    return 0;
}

标签:rt,tmp,ch,浅谈,int,Splay,fa,节点
From: https://www.cnblogs.com/NtYester/p/16783416.html

相关文章

  • 浅谈后缀平衡树
    1.0写在前面原题目为:浅谈后缀平衡树以及关于后缀平衡树非惰性删除操作正确性的思考和惰性删除操作的实现。本文的主题是我对于后缀平衡树非惰性删除操作正确性的思考(就......
  • 浅谈替罪羊树
    前言噫,好!我又来了这几天闲着没事翻桃片,发现好多人卡评测被封了,就看了看紫荆花,发现splay会被卡,就去学了替罪羊树(\(\text{ScapegoatTree}\))。正文写在前面大家都知道......
  • 浅谈MySQL、Hadoop、BigTable、Clickhouse数据读写机制
    个人理解,欢迎指正数据库引擎写数据读数据补充MySqlInnoDB:支持事务,高速读写性能一般Myisam:不支持事务,高速读写性能好以InnoDB更新一条记录为例1、B+Tree......
  • 浅谈LIS和LCS
    这是一篇由超级菜的OIer写的博客......LIS就是最长上升子序列,通过DP求解。普通的求法是\(O({n}^{2})\)的(相信大家都会写):解法1#include<bits/stdc++.h>usingnames......
  • 浅谈一下山海鲸和镝数这2款软件
    “数据可视化”无疑是当下被讨论的最多的话题之一,我也经常在各个平台看到有人提及。近日,我在翻阅知乎有关于可视化工具时经常能够看到两个名字:“山海鲸可视化”和“镝数图......
  • 浅谈自定义事件如何创建,触发和监听?
    我们知道的原生js事件,即内置事件有click,blur,mousemove等等。那如果我们想自定义一个事件呢?1、通过newEvent创建一个事件实例2、触发事件通过dispatch进行事件分发3......
  • 浅谈Go1.18版本新特性——泛型
    泛型Generics:引入了对使用参数化类型的泛型代码的新支持,达到了算法可复用的目的。两数求和,泛型函数的使用假设我们要计算两个数的和,函数可以这样子写funcAdd(a......
  • 浅谈gedit配置
    \(\text{gedit}\)是\(\text{GNOME}\)桌面的小型和轻量文本编辑器因为之前看学长博客感觉燕大的电脑似乎比较[数据删除],于是去问了\(\text{CDsidi}\)大佬\(\text{v......
  • CSS——display(元素显示)
     display是可以更改默认的元素类型,默认的类型一般有:1.块级元素(blockelement)特点:块级元素总是从新行开始,并占据可用的全部宽度。<div><h1>-<h6><p><form><hea......
  • 浅谈城市综合管廊分类及其运维管理
    陈盼安科瑞电气股份有限公司上海嘉定 20220620一、前言    综合管廊(日本称“共同沟”、中国台湾称“共同管道”),就是地下城市管道综合走廊,即在城市地下建造一个隧......