可并堆之左偏树浅谈

白昼怎懂夜的黑 提交于 2019-12-03 05:02:50

左偏树是用来快速地合并堆的

正常的堆是一颗完全二叉树,我们用笨方法去合并它:

假设我们要将x和y这两个小根堆合并,我们判断一下如果x的堆顶大于y的堆顶,就交换一下x和y,然后继续合并x的某个子孩子和y。

 

堆被人们所推广的原因就是因为它的时间复杂度比较稳定,根本原因是堆是一颗完全二叉树

但显然的:这样合并堆并没有保证时间复杂度,也就是说没有维护完全二叉树的形态;

这时候解决的办法之一便是利用左偏树;

它比普通的堆多了一个性质:向左偏;

注意,这里的向左偏并不是指子树的大小向左偏,而是最大深度向左偏;

为了方便我们理解,我们引入一下几种概念:

我们这里定义一个值,叫做"根值",一个节点的根值就是它到最近的叶子节点的距离;

我们保证,任意一个节点的左儿子的根值大于等于右儿子的根值;

这样我们会得到一个性质:
一个n个节点的左偏树距离最大为log(n+1)−1

简易论证:

  若左偏树的根值为一定值,则节点数最少的左偏树是完全二叉树

  若一棵左偏树的距离为k,则这棵左偏树至少有2^(k+1)-1个节点;

这样做的时间复杂度是O(logn),我们可以接受;(并且没用STL,常数也很小)

为了更加的优化程序,我们可以使用路径压缩来快速找到每个元素属于哪个根

#include <bits/stdc++.h>
#define inc(i,a,b) for(register int i=a;i<=b;i++)
using namespace std;
int n,m;
int dis[1000010],root[1000010],lson[1000010],rson[1000010];
template<class nT>
inline void read(nT& x)
{
	char c;while(c=getchar(),!isdigit(c));
	x=c^48;while(c=getchar(),isdigit(c)) x=x*10+c-48;
}
struct node{
	int pos;
	int value;
}tree[1000010];
int judge[10000010];
int find(int x)
{
	if(root[x]==x){
		return x;
	}
	return root[x]=find(root[x]);
}
int merge(int x,int y)
{
	if(!x||!y) return x+y;
	if(tree[x].value==tree[y].value){
		if(tree[y].pos<tree[x].pos){
			swap(x,y);
		}
	}
	else if(tree[y].value<tree[x].value){	
		swap(x,y);
	}
	rson[x]=merge(rson[x],y);
	if(dis[lson[x]]<dis[rson[x]]) swap(lson[x],rson[x]);
	dis[x]=dis[rson[x]]+1;
	return x;	
}
int main()
{
	dis[0]=-1;
	cin>>n>>m;
	inc(i,1,n){
		read(tree[i].value);
		root[i]=i; tree[i].pos=i;
	}
	inc(i,1,m){
		int type,x,y;
		read(type); read(x);
		if(type==1){
			read(y);
			if(judge[x]||judge[y]) continue;
			x=find(x); y=find(y);
			if(x==y) continue;
			root[x]=root[y]=merge(x,y);
		}
		else{
			if(judge[x]){
				cout<<"-1"<<endl;
			}
			else{
				x=find(x);
				cout<<tree[x].value<<endl;
				judge[x]=1;
				root[lson[x]]=root[rson[x]]=root[x]=merge(lson[x],rson[x]);
				lson[x]=rson[x]=dis[x]=0;
			}
		}
	}
}

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!