zoukankan      html  css  js  c++  java
  • Splay

    上午刚学了Splay,写篇博客
    首先感谢yyb大佬的讲解;
    另外,这篇博客也有多处调用;

    基本旋转操作

    直接看大佬总结:
    1.X变到原来Y的位置
    2.Y变成了 X原来在Y的 相对的那个儿子
    3.Y的非X的儿子不变 X的 X原来在Y的 那个儿子不变
    4.X的 X原来在Y的 相对的 那个儿子 变成了 Y原来是X的那个儿子

    void rotate(int x){
    	int y=t[x].ff;
    	int z=t[y].ff;
    	int k=t[y].ch[1]==x;//k表示x在y的哪个儿子,0表示左二子,1表示右儿子; 
    	t[z].ch[t[z].ch[1]==y]=x;//y原来的位置变为x; 
    	t[x].ff=z;//x的父亲变为z; 
    	t[y].ch[k]=t[x].ch[k^1];//与k相反方向的x的儿子,成为为y的k方向的儿子; 
    	t[t[x].ch[k^1]].ff=y;
    	t[x].ch[k^1]=y;//k相反方向的x的儿子变为y; 
    	t[y].ff=x;
    	up(y),up(x);//旋转完之后siz会改变 ,所以更新一下 ,注意先更新y,再更新x;
    }
    

    Splay操作

    大佬总结的很精辟
    对于XYZ的不同情况,可以自己画图考虑一下,
    如果要把X旋转到Z的位置应该如何旋转

    归类一下,其实还是只有两种:
    第一种,X和Y分别是Y和Z的同一个儿子
    第二种,X和Y分别是Y和Z不同的儿子

    对于情况一,也就是类似上面给出的图的情况,就要考虑先旋转Y再旋转X
    对于情况二,自己画一下图,发现就是对X旋转两次,先旋转到Y再旋转到X

    这样一想,对于splay旋转6种情况中的四种就很简单的分了类
    其实另外两种情况很容易考虑,就是不存在Z节点,也就是Y节点就是Splay的根了
    此时无论怎么样都是对于X向上进行一次旋转

    void splay(int x,int goal){
    	while(t[x].ff!=goal){
    		int y=t[x].ff,z=t[y].ff;
    		if(z!=goal) (t[z].ch[0]==y)^(t[y].ch[0]==x)?rotate(x):rotate(y);//不同方向旋转y,相同旋转x; 
    		rotate(x);//最后都要旋转y; 
    	}
    	if(goal==0) root=x;//别忘了这里 
    }
    

    插入操作

    先找一下这个数的位置,再判断这个位置是否有值

    void insert(int x){
    	int u=root,ff=0;
    	while(u&&t[u].val!=x){
    		ff=u;
    		u=t[u].ch[x>t[u].val];//不短找 
    	}
    	if(u) t[u].cnt++;//如果之前这个节点存在,直接cnt++; 
    	else {
    		u=++tot;//否则新建一个节点; 
    		if(ff) t[ff].ch[x>t[ff].val]=u;
    		t[u].ch[0]=t[u].ch[1]=0;
    		t[u].cnt=1;
    		t[u].siz=1;
    		t[u].ff=ff;
    		t[u].val=x;
    	}
    	splay(u,0);
    }
    

    find函数(直接上代码了)

    注意这个操作是把x这个数换到根;

    void find(int x){
    	int u=root;
    	if(!u)return;//空树直接返回;
    	while(t[u].ch[x>t[u].val]&&t[u].val!=x){
    		u=t[u].ch[x>t[u].val];//不断寻找; 
    	}
    	splay(u,0);
    }
    

    前驱与后继

    首先就要执行find操作
    把要查找的数弄到根节点
    然后,以前驱为例
    先确定前驱比他小,所以在左子树上
    然后他的前驱是左子树中最大的值
    所以一直跳右结点,直到没有为止
    找后继反过来就行了

    int nxt(int x,int f){ //f=1表示查找后继,0表示查找前驱; 
    	find(x);//先把x旋转到根位置; 
    	int u=root;
    	if(t[u].val>x&&f) return u;
    	if(t[u].val<x&&!f) return u;//可以直接返回 
    	u=t[u].ch[f];
    	while(t[u].ch[f^1]) u=t[u].ch[f^1]; //要反着跳转,否则会越来越大(越来越小) 
    	return u;
    }
    

    删除(直接放代码了)

    注意别忘了修改siz

    void Del(int x){
    	int nt=nxt(x,1);//查找x的后继
    	int last=nxt(x,0);//查找x的前驱
    	splay(last,0);
    	splay(nt,last); 
    	//将前驱旋转到根节点,后继旋转到根节点下面
        //很明显,此时后继是前驱的右儿子,x是后继的左儿子,并且x是叶子节点
    	int del=t[nt].ch[0];
    	if(t[del].cnt>1){
    	   	t[del].cnt--;//存在多个这个数字,直接减去一个 
    		splay(del,0);
        }
        else t[nt].ch[0]=0,t[nt].siz--,t[last].siz--;//清除掉节点,由于知道del上方的节点都有什么,直接修改即可 
    }
    

    排名第k的数

    int k_th(int x){
    	int u=root;
    	if(t[u].siz<x) return 0;
    	while(1){
    		int y=t[u].ch[0];
    		if(x>t[y].siz+t[u].cnt){ //如果排名比左儿子的大小和当前节点的数量要大
    			x-=t[y].siz+t[u].cnt;
    			u=t[u].ch[1]; //那么当前排名的数一定在右儿子上找
    		} else {
    			if(x<=t[y].siz) u=y; //左儿子的节点数足够,在左儿子上继续找
    			else return t[u].val; //否则就是在当前根节点上
    		}
    	}
    }
    

    总代码

    #include<iostream>
    #include<cstdio>
    using namespace std;
    const int N=1e5+7;
    struct node{
    	int cnt,siz,val,ff,ch[2];
    }t[N];
    int n,root,tot;
    void up(int x){
    	t[x].siz=t[x].cnt+t[t[x].ch[0]].siz+t[t[x].ch[1]].siz;
    }
    void rotate(int x){
    	int y=t[x].ff;
    	int z=t[y].ff;
    	int k=t[y].ch[1]==x;//k表示x在y的哪个儿子,0表示左二子,1表示右儿子; 
    	t[z].ch[t[z].ch[1]==y]=x;//y原来的位置变为x; 
    	t[x].ff=z;//x的父亲变为z; 
    	t[y].ch[k]=t[x].ch[k^1];//与k相反方向的x的儿子,成为为y的k方向的儿子; 
    	t[t[x].ch[k^1]].ff=y;
    	t[x].ch[k^1]=y;//k相反方向的x的儿子变为y; 
    	t[y].ff=x;
    	up(y),up(x);//旋转完之后siz会改变 ,所以更新一下 ,注意先更新y,再更新x;
    }
    
    void splay(int x,int goal){
    	while(t[x].ff!=goal){
    		int y=t[x].ff,z=t[y].ff;
    		if(z!=goal) (t[z].ch[0]==y)^(t[y].ch[0]==x)?rotate(x):rotate(y);//不同方向旋转y,相同旋转x; 
    		rotate(x);//最后都要旋转y; 
    	}
    	if(goal==0) root=x;//别忘了这里 
    }
    
    void insert(int x){
    	int u=root,ff=0;
    	while(u&&t[u].val!=x){
    		ff=u;
    		u=t[u].ch[x>t[u].val];//不短找 
    	}
    	if(u) t[u].cnt++;//如果之前这个节点存在,直接cnt++; 
    	else {
    		u=++tot;//否则新建一个节点; 
    		if(ff) t[ff].ch[x>t[ff].val]=u;
    		t[u].ch[0]=t[u].ch[1]=0;
    		t[u].cnt=1;
    		t[u].siz=1;
    		t[u].ff=ff;
    		t[u].val=x;
    	}
    	splay(u,0);
    }
    
    void find(int x){
    	int u=root;
    	if(!u)return;//空树直接返回;
    	while(t[u].ch[x>t[u].val]&&t[u].val!=x){
    		u=t[u].ch[x>t[u].val];//不断寻找; 
    	}
    	splay(u,0);
    }
    
    int nxt(int x,int f){ //f=1表示查找后继,0表示查找前驱; 
    	find(x);//先把x旋转到根位置; 
    	int u=root;
    	if(t[u].val>x&&f) return u;
    	if(t[u].val<x&&!f) return u;//可以直接返回 
    	u=t[u].ch[f];
    	while(t[u].ch[f^1]) u=t[u].ch[f^1]; //要反着跳转,否则会越来越大(越来越小) 
    	return u;
    }
    
    void Del(int x){
    	int nt=nxt(x,1);//查找x的后继
    	int last=nxt(x,0);//查找x的前驱
    	splay(last,0);
    	splay(nt,last); 
    	//将前驱旋转到根节点,后继旋转到根节点下面
        //很明显,此时后继是前驱的右儿子,x是后继的左儿子,并且x是叶子节点
    	int del=t[nt].ch[0];
    	if(t[del].cnt>1){
    	   	t[del].cnt--;//存在多个这个数字,直接减去一个 
    		splay(del,0);
        }
        else t[nt].ch[0]=0,t[nt].siz--,t[last].siz--;//清除掉节点,由于知道del上方的节点都有什么,直接修改即可 
    }
    
    int k_th(int x){
    	int u=root;
    	if(t[u].siz<x) return 0;
    	while(1){
    		int y=t[u].ch[0];
    		if(x>t[y].siz+t[u].cnt){ //如果排名比左儿子的大小和当前节点的数量要大
    			x-=t[y].siz+t[u].cnt;
    			u=t[u].ch[1]; //那么当前排名的数一定在右儿子上找
    		} else {
    			if(x<=t[y].siz) u=y; //左儿子的节点数足够,在左儿子上继续找
    			else return t[u].val; //否则就是在当前根节点上
    		}
    	}
    }
    
    int main(){
    	insert(-2147483647);//蒟蒻不知道为什么要加这两行,不过不加会错 
        insert(+2147483647);
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++){
    		int opt,x;
    		scanf("%d%d",&opt,&x);
    		if(opt==1) insert(x);
    		if(opt==2) Del(x);
    		if(opt==3) {find(x),cout<<t[t[root].ch[0]].siz<<"
    ";}//因为有一个极小值,所以不用加一; 
    		if(opt==4) cout<<k_th(x+1)<<"
    ";//因为加了一个极小值,所以第x+1个,就是要求的第x个; 
    		if(opt==5) cout<<t[nxt(x,0)].val<<"
    ";
    		if(opt==6) cout<<t[nxt(x,1)].val<<"
    ";
    	}
    }
    
  • 相关阅读:
    为什么新买的音响连接上电脑后有很强的杂音(电流声)?
    USB2.0 Camera驱动
    如何重命名多张图片的名称
    网络共享:[25]组策略
    线刷宝“华为荣耀畅玩7 (DUA-AL00)”刷机教程
    电信手机卡被锁,忘记了PIN码,怎么办?
    [转]glyphicons-halflings-regular字体 图标
    牛年祝福语大全
    HYQiHei GES字体汉仪旗黑全套字体
    得实AR-530K打印机驱动 vB5.5官方版
  • 原文地址:https://www.cnblogs.com/Aswert/p/13618308.html
Copyright © 2011-2022 走看看