Splay树、Treap树

1、Splay树

复杂度都为O(NlogN)

特点:

(1)允许任意节点旋转到根(经常查询或使用这个数)

(2)当需要分裂和合并的时候非常方便

操作:

1、提根splay

根据x的位置,可以分为3种类型

1)x的父节点就是根,旋转一次就可以

2)x的父节点,的父节点,三点共线:先旋转x的父节点,在旋转x

3)x的父节点,的父节点,三点不共线:把x按不同的方向旋转2次

2、插入:插入后进行splay

3、分裂:以第k小的节点为中介,分成两棵树,先把第k小的节点旋转到根,然后与右子树分开就可以了

4、合并:假设合并的其中一颗树所有节点都小于另一棵,然后把小的那棵最大的旋转到根,然后和另一棵合并

5、删除:删除节点旋转到根,然后删除,合并左右子树

hdu 1890(这个不是裸体,只是利用了旋转功能

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=101000;
const int INF=0x3fffffff;
typedef long long LL;
//建树:
//用Splay旋转到根,其左子树的大小即排在左边的个数 ,输出就可以
//翻转左子树:用了线段树的思想:进行标记,而不是直接操作,等splay操作的时候在处理
//标记函数:updat_rev(),
//删除根:根据标记进行子树的翻转 
int rev[maxn]; //标记i被翻转
int pre[maxn]; //i的父节点
int size[maxn];  //i的子树上节点的个数
int tree[maxn][2] ; //记录树,0左,1右
int root;
struct node{
	int val,id;
	bool operator < (const node &a)const{
		if(val==a.val) return id<a.id;
		else return val<a.val;
	}
}nodes[maxn]; 

void push_up(int x){   //记录以x为根的子树包含的节点 
	size[x]=size[tree[x][0]]+size[tree[x][1]]+1;
}
//进行旋转 
void updata_rev(int x){
	if(!x) return; //NULL
	swap(tree[x][0],tree[x][1]);
	rev[x]^=1;   //记录已经更新了 
}
void pushdown(int x){
	if(rev[x]){   //如果标记了,就旋转,一般用在 rotate和splay 
		updata_rev(tree[x][0]); 
		updata_rev(tree[x][1]);
		rev[x]=0;
	}
}
void Rotate(int x,int c){  //c=0左旋, c=1右旋 
	int y=pre[x];
	pushdown(y);
	pushdown(x);    //先更新 
	//然后就是更改和更新信息
	//1、改变父子结构
	tree[y][!c]=tree[x][c];   //y的儿子 
	pre[tree[x][c]]=y;        //x的儿子重新认爸爸
	if(pre[y]){
		tree[pre[y]][tree[pre[y]][1]==y]=x;  //改变儿子,y的爸爸
		                //原来是哪边,现在还是哪边 
	} 
	pre[x]=pre[y]; //x也重新仁爸爸 
	tree[x][c]=y;  //儿子
	pre[y]=x;
	push_up(y); 
}
void splay(int x,int goal){  //把节点x作为goal的孩子,如果goal为0,那么就是旋转到跟
	pushdown(x); 
 	while(pre[x]!=goal){  //一直旋转,知道成为goal的孩子
	 //区分三种情况
	 if(pre[pre[x]]==goal){  //1、父节点是根,直接旋转一次就可以
	 	pushdown(pre[x]);
	 	pushdown(x); 
	 	Rotate(x,tree[pre[x]][0]==x); //左孩子:右旋,右孩子:左旋 
	 } 
	 else{  //父节点不是根 
	 	pushdown(pre[pre[x]]);
	 	pushdown(pre[x]);  //先更新 
	 	pushdown(x);
	 	int y=pre[x];
	 	int c=(tree[pre[y]][0]==y);  //先判断父节点是左还是右孩子
		if(tree[y][c]==x){  //三点不共线!!!!  把x按不同的方向旋转2次
			Rotate(x,!c);
			Rotate(x,c);
		} 
		else{  //三点共线 先旋转x的父节点,在旋转x
			Rotate(y,c);  
			Rotate(x,c);
		}
	 }
}
	 push_up(x);
	 if(goal==0) root=x; //如果goal是0,那么就更新根节点 
} 

//接下来是删除节点操作
int get_max(int x){
	pushdown(x);  //更新
	while(tree[x][1]) {
		x=tree[x][1];     //找前驱:一直在做孩子的右节点找:BST 
		pushdown(x);  
	}
	return x;
} 

void del_node(){
	//删除根节点(通过前面的操作,已经把需要删除的节点放在根节点了 
	if(tree[root][0]==0){ //没有左孩子:没有前驱,而且可以直接删掉 
		root=tree[root][1];
		pre[root]=0;
	}
	else{
		int m=get_max(tree[root][0]);  //找到前驱
		splay(m,root);  //提根
		tree[m][1]=tree[root][1];  //右孩子=另一棵树(相当于合并两棵树
		pre[tree[root][1]]=m; //更新爸爸
		root=m; 
		pre[root]=0;
		push_up(root);
	}
}
void newnode(int &x,int fa,int val){  //新建节点
	x=val;
	pre[x]=fa;
	size[x]=1;
	rev[x]=0;
	tree[x][0]=tree[x][1]=0;  //全体初始化 
}
void buildtree(int &x,int l,int r,int fa){  //建树 
	if(l>r) return;
	int mid=(l+r)>>1; //中间开始建(平衡)
	newnode(x,fa,mid);  //先按照初始位置建树!!!
	buildtree(tree[x][0],l,mid-1,x);
	buildtree(tree[x][1],mid+1,r,x);
	push_up(x); 
}
void inti(int n){
	root=0;
	tree[root][0]=tree[root][1]=pre[root]=size[root]=0;
	buildtree(root,1,n,0);
}

int main(){
	int n;
	while(~scanf("%d",&n)&&n){
		inti(n);
		for(int i=1;i<=n;i++){
			scanf("%d",&nodes[i].val);
			nodes[i].id=i;
		}
		sort(nodes+1,nodes+1+n);
		for(int i=1;i<n;i++){  //只进行n-1次操作 
			splay(nodes[i].id,0);   //第i次旋转:把第i大的节点旋转到根 
			updata_rev(tree[root][0]); //左子树需要旋转
			printf("%d ",i+size[tree[root][0]]);  
			//第i个被翻转的数的左边的数,就是左子树的个数
			del_node(); 
		}
		printf("%d\n",n);
	}
return 0;
}

  Treap树

数和堆的集合,每个节点有值,也有优先级,那么这棵树的形态就被确定了,和插入顺序无关了(有赖于优先级

避免退化成链:再生成节点时,随机生成优先级,然后插入时动态调整

删除:如果有两个子节点,找到优先级大的,把x向反方向旋转,也就是把x向树的下层调整,直到旋转到叶子节点

!很多题目涉及名次树

常用操作:

struct node,旋转rotate,插入insert(),查找第k大的数kth(),查询某个数find()【名次树】

猜你喜欢

转载自www.cnblogs.com/shirlybaby/p/12393268.html