zoukankan      html  css  js  c++  java
  • 题解 洛谷 P3369 【【模板】普通平衡树】

    指针Splay题解

    前言

    Splay是像我这样的小蒟蒻一开始学的平衡树。

    虽然Splay常数不小,但是功能十分全面,既可以当区间树也可以当平衡树(当然这两者不可兼顾)

    看到题解里一堆dalao写数组,但是Splay本来是应该用指针实现的(据说这样常数会小很多),于是蒟蒻就开始写起了指针Splay。。。

    出人意料,数组Splay我一遍A,指针我居然调试了将近一年(真事)我果然太蒟了似乎明白了大家为什么都不用指针

    但是,从一遍遍的调试之间,我还是学到了不少东西,比如如何增加代码的可调试性,如何考虑到方方面面以防止RE。这可能也是一种进步。最后发现,其实指针Splay也挺好(nan)打,更重要的是,指针也更方便理解。

    为了希望大家少走点弯路.带大家入调试大坑本蒟蒻觉得有必要写一份详细的指针Splay题解.

    大部分资料来源于网络。

    Spaly最快了233

    正文

    (本文假设大家都对二叉查找树有基本理解,不再赘述一些常识.网上的讲的比我好多了)(本文默认节点左小右大)

    一些前置函数

    获取节点的大小

    inline unsigned int size(tree x){return x?x->size:0;}/*防止x为空导致访问NULL的信息(RE)*/
    

    维护节点大小

    inline void pushup(tree x){x->size=size(x->ch[0])+size(x->ch[1])+x->cnt;}//一个节点的大小等于它的左子树的大小+右子树大小+本身的个数(可以有重复)
    

    获取节点是它父亲的那个儿子

    inline bool wson(tree son,tree par)//0为左儿子,一为右儿子
    {
    	if(!par)return 0;//父亲为空防止RE
    	return par->ch[1]==son;
    }
    

    建立父子关系

    inline void buildfather(tree son,tree par,bool which)//0表示变为左儿子,1表示变为右儿子
    {
    	if(son)son->par=par;
    	if(par)par->ch[which]=son;
    	else root=son;//如果父亲为空,自然其为根。
    }
    

    旋转操作

    旋转操作是Splay Tree的核心操作
    它通过旋转在不破坏BST的性质的情况下,调整树的结构。

    图中可以看出C>Q>B>P>A.

    我们的目标是将P转到Q的位置

    直接交换肯定不行,这破坏了BST的性质。

    事实上,我们可以看出,A与C的位置一定是固定的。

    P,Q是我们需要交换的节点,所以我们可以通过对B进行换位来保证BST的性质不被破坏。以右旋为例。右图仍然有C>Q>B>P>A.

    这样就成功实现了上旋的操作

    个人习惯将左右旋转写在一个函数中

    inline void rotate(tree x)
    {
    	tree p=x->par,g=p->par;//这里导致我调试了一年。
    	bool r=wson(x,p);//本来我仿照的数组的题解,将wson(x)表示x为它的父亲的哪一个孩子。但是旋转过程中父子关系产生了变化,于是就必须提前记录好par,与grandpar.
    	buildfather(x,g,wson(p,g));//将x提到p的位置。就必须将g的儿子(p)的位置变成x。所以这里用了wson(p,g).
    	buildfather(x->ch[!r],p,r);//x->ch[!r]即为图中的B.
    	buildfather(p,x,!r);//将p设为x的儿子,在原来B的位置上
    	pushup(p);//这里为什么不需要pushup(x),Splay函数会给出相关解释
    }
    

    Splay操作

    Splay是SplayTree的核心操作(废话)。
    它通过rotate,单旋与双旋来维护Splay Tree的深度。
    如果不理解双旋(或者是Spaly教的忠实信奉着)可以参考网上资料

    这里因为是主要讲解指针,所以不再赘述。

    inline void Splay(tree x,tree y)//将x转到y的下方
    {
    	while(x->par!=y)//直到x的父亲是y.
    	{
    		tree p=x->par,g=p->par;//同rotate及时预处理好p与g.(虽然这里没有必要)
    		if(x->par->par!=y)wson(x,p)^wson(p,g)?rotate(x):rotate(p);//如果成一条链就先转p,反之转两次x
            rotate(x);
    	}
    	pushup(x);//填坑,因为你每一次rotate都将x向上转,那么你的x的size一定会一直改变,所以在rotate里面pushup(x)没有意义。只需要在最后pushup(x)即可。
    }
    

    Insert操作

    建树时,当然可以预先读入数据构建完美的Splay.
    这里只给出Insert.毕竟复杂度也没差多少

    inline void insert(int val)
    {
    	if(!root)//特判root为空情况
    	{
    		root=new node(val,NULL);
    		return;
    	}
    	for(tree x=root;x;x=x->ch[val>=x->val])//从root向下插入,每次判断应当走哪边
    	{
    		if(x->val==val)//如果已经有这个元素了。则cnt++.
    		{
    			x->cnt++;
    			Splay(x,NULL);//维护Splay深度
    			return;
    		}
    		if(!x->ch[val>=x->val])//如果到了空,也就是没有这个节点
    		{
    			x->ch[val>=x->val]=new node(val,x);//新建一个节点,C++的new和delete较慢,这里为了友好一点就不用内存池了233.
    			Splay(x->ch[val>=x->val],NULL);
    			return;
    		}
    	}
    }
    

    Find操作

    同普通BST

    inline void find(int val)
    {
    	tree x=root;
    	while(x/*root为空*/&&x->ch[val>x->val]/*这里因为要精确查找所以将>=分开成了>与=两种情况*/&&val!=x->val/*找到了*/)x=x->ch[val>x->val];
    	if(x)Splay(x,NULL);//记得在每个操作都要Splay
    }
    

    Delete操作

    这是一个比较复杂的点.

    你当然也可以将前驱旋转到根,后继旋转到右子树,然后直接删除右子树的左子树。但这样需要提前插入INF与-INF(或特判),个人认为比较容易出错,毕竟你的Splay里多了两个节点,findkth(k),需要将k++(INF永远比你大))

    详见注释

    inline int del(int val)
    {
    	find(val);//将值为val的点转到根 
    	if(root->val!=val)return;//找不到
    	tree x=root;
    	if(x->cnt>1)x->cnt--;//多于一个。 
    	else 
    	if(!x-ch[0])//有一子树为空 
    	{
    		root=x->ch[1];//root转移到另外一边 
    		if(root)root->par=NULL;//防止RE. 
    		delete x;//回收x 
    	}
    	else//两子树都非空 
    	{
    		tree k=x->ch[0];//找到左子树中最大的一个 
    		while(k->ch[1])k=k->ch[1];
    		Splay(k,x);//将他旋转到左子树的根上 
    		root=k,root->par=NULL;//这个时候左子树的右子树肯定为空 
    		buildfather(x->ch[1],root,1);//再将右子树转移到左子树的右子树 
    		delete x;//回收x 
    	}
    }
    

    这里理解可能有些难度,可以考虑手玩一下。

    kth操作

    这个不难,但是一定要画出图,不能想当然!

    inline int findkth(int k)
    {
    	tree x=root;
    	assert(size(x)>=k);//元素个数一定要至少有k个,且没有第0大 
        assert(k);//你可以选择无视这两句话
    	while(x)
    	{
    		if(size(x->ch[0])+x->cnt>=k&&size(x->ch[0])<k)return x->val;//如果你左子树的大小加上你的个数>=k并且你左子树的大小<k那么你就是第k大
    		if(size(x->ch[0])>=k)x->ch[0];//如果左子树大小
    		else k-=size(x->ch[0])+x->cnt,x=x->ch[1]; //
    	}
    	return -2147483647;//出现未知错误 (树的构建可能出现问题)(如果你其他地方正确,这里并不会有用)
    }	
    

    前驱与后继(pre&nxt)操作

    前驱就是比你小的最大的一个
    后继就是比你大的最小的一个
    所以前驱就是左子树的最右一个
    后继同理
    这里只对于前驱做出说明

    inline int pre(int val)
    {
    	insert(val);//插入val,它会被转到root
    	tree x=x->root->ch[0];
    	while(x->ch[1])x=x->ch[1];//找到比val小的最大的数
    	del(val);//再删掉
    	return x->val;
    }
    inline int nxt(int val)
    {
    	insert(val);
    	tree x=x->root->ch[1];
    	while(x->ch[0])x=x->ch[0];
    	del(val);
    	return x->val;
    }
    

    完整代码

    #include<bits/stdc++.h>
    using namespace std;
    #define IL inline
    #define RG register
    #define gi getint()
    #define pi(k) putint(k)
    #define gc getchar()
    #define File(a) freopen(a".in","r",stdin);freopen(a".out","w",stdout)
    IL int getint()
    {
        RG int xi=0;
        RG char ch=gc;
        bool f=0;
        while(ch<'0'|ch>'9')ch=='-'?f=1:f,ch=gc;
        while(ch>='0'&ch<='9')xi=(xi<<1)+(xi<<3)+ch-48,ch=gc;
        return f?-xi:xi;
    }
    IL void putint(int k)
    {
        if(k<0)k=-k,putchar('-');
        if(k>=10)putint(k/10);
        putchar(k%10+'0');
    }
    struct SplayTree{
    	struct node;
    	typedef node* tree;
    	struct node{
    		tree ch[2],par;
    		int size,val,cnt;
    		node(int value,tree fa)
    		{
    			val=value,par=fa;
    			size=1,cnt=1;
    			ch[0]=ch[1]=NULL; 
    		}
    	}*root;
    	SplayTree(){root=NULL;}
    	inline int size(tree x){return x?x->size:0;}
    //	#define size(x) (x?x->size:0)
    	inline void pushup(tree x){if(x)x->size=size(x->ch[0])+size(x->ch[1])+x->cnt;}
    	inline void buildfather(tree son,tree par,bool which)
    	{
    		if(son)son->par=par;
    		if(par)par->ch[which]=son;
    		else root=son;
    	}
    	inline bool wson(tree son,tree par)
    	{
    		if(!par)return 0;
    		return par->ch[1]==son;
    	}
    	inline void rotate(tree x)
    	{
    		tree p=x->par,g=p->par;
    		bool r=wson(x,p);
    		buildfather(x,g,wson(p,g));
    		buildfather(x->ch[!r],p,r);
    		buildfather(p,x,!r);
    		pushup(p);
    	}
    	inline void Splay(tree x,tree y)
    	{
    		while(x->par!=y)
    		{
    			tree p=x->par,g=p->par;
    			if(x->par->par!=y)wson(x,p)^wson(p,g)?rotate(x):rotate(p);
    			rotate(x);
    		}
    		pushup(x);
    	}
    	inline void insert(int val)
    	{
    		if(!root)
    		{
    			root=new node(val,NULL);
    			return;
    		}
    		for(tree x=root;x;x=x->ch[val>=x->val])
    		{
    			if(x->val==val)
    			{
    				x->cnt++;
    				Splay(x,NULL);
    				return;
    			}
    			if(!x->ch[val>=x->val])
    			{
    				x->ch[val>=x->val]=new node(val,x);
    				Splay(x->ch[val>=x->val],NULL);
    				return;
    			}
    		}
    	}
    	inline void find(int val)
    	{
    		tree x=root;
    		while(x&&x->ch[val>x->val]&&val!=x->val)x=x->ch[val>x->val];
    		if(x)Splay(x,NULL);
    	}
    	inline int findkth(int k)
    	{
    		tree x=root;
    		assert(size(x)>=k);
            assert(k);
    		while(x)
    		{
    			if(size(x->ch[0])+x->cnt>=k&&size(x->ch[0])<k)return x->val;
    			if(size(x->ch[0])>=k)x=x->ch[0];
    			else k-=size(x->ch[0])+x->cnt,x=x->ch[1]; 
    		}
    		return -2147483647;
    	}
    	inline void del(int val)
    	{
    		find(val);
    		if(root->val!=val)return;
    		tree x=root;
    		if(x->cnt>1)x->cnt--;
    		else 
    		if(!x->ch[0])
    		{
    			root=x->ch[1];
    			if(root)root->par=NULL; 
    			delete x;
    		}
    		else
    		{
    			tree k=x->ch[0];
    			while(k->ch[1])k=k->ch[1];
    			Splay(k,x);
    			root=k,root->par=NULL;
    			buildfather(x->ch[1],root,1); 
    			delete x;
    		}
    	}
    	inline int pre(int val)
    	{
    		insert(val);
    		tree x=root->ch[0];
    		while(x->ch[1])x=x->ch[1];
    		del(val);
    		return x->val;
    	}
    	inline int nxt(int val)
    	{
    		insert(val);
    		tree x=root->ch[1];
    		while(x->ch[0])x=x->ch[0];
    		del(val);
    		return x->val;
    	}
    }bt; 
    int main(void)
    {
    	int n=gi;
        int a;
        for(RG int i=1; i<=n; i++)
            switch(gi)
            {
                case 1:
                    a=gi;
                    bt.insert(a);
                    break;
                case 2:
                    a=gi;
                    bt.del(a);
                    break;
                case 3:
                    a=gi;
                    bt.find(a);
                    printf("%d
    ",bt.size(bt.root->ch[0])+1);//这能理解吗
                    break;
                case 4:
                    a=gi;
                    printf("%d
    ",bt.findkth(a));
                    break;
                case 5:
                    a=gi;
                    printf("%d
    ",bt.pre(a));
                    break;
                case 6:
                    a=gi;
                    printf("%d
    ",bt.nxt(a));
                    break;
            }
        return 0;
    }
    
  • 相关阅读:
    13/6/21 Hella Intern Interview
    【ToReadList】六种姿势拿下连续子序列最大和问题,附伪代码(以HDU 1003 1231为例)(转载)
    【算法】[leetcode] permutations的讨论(转载)
    【算法】leetcode之 Palindrome Partitioning I&II(转载)
    【C++】从最简单的vector中sort用法到自定义比较函数comp后对结构体排序的sort算法
    【C++】STL :栈
    【算法】逆波兰表达式
    【C++】STL,vector容器操作
    【LINUX】降级安装低版本GCC,G++
    【NS2】在linux下安装低版本GGC
  • 原文地址:https://www.cnblogs.com/LLCSBlog/p/10202367.html
Copyright © 2011-2022 走看看