题解 洛谷 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;
}
原文地址:https://www.cnblogs.com/LLCSBlog/p/10202367.html