配对堆

本文介绍了配对堆这一数据结构,它是解决堆合并问题的一种优化方案。配对堆通过儿子左兄弟的方式存储多叉树,并利用并查集维护堆的独立性。在合并操作上,配对堆实现了常数时间复杂度,而在弹出堆顶元素时,通过平均复杂度保证了效率。此外,配对堆还适用于Dijkstra算法等场景,但需要添加decrease_key操作。文章提供了配对堆的C++实现,并展示了如何进行push、pop、top等操作。
摘要由CSDN通过智能技术生成

配对堆

在stl里的priority——queue是一个很好用的堆,但是假如说我们要将两个堆去进行合并的话,普通的堆的时间和空间复杂度都大大超标,这时候我们就有了可合并堆,我个人认为配对堆会好些一点,所以就放弃左偏堆了(

基本原理

我们知道一个多叉树的存树方式 是父亲指向儿子,我们的配对堆其实也是一个多叉树,但是它的存树方式是儿子左兄弟,也就是我们用一个结构体去存堆

struct heap{
	int r;//right
	int s;//son
	int x;//value
};
int fa[100];

如果有需要还可以加上并查集去判断每一个点是否处在一个堆中(最好还是加上)

merge

合并的复杂度是o(1) 是不是很难得,我们只用找到两个数所在堆的堆顶,之后如果有0就返回非0那个,假如是小根堆的话就把大的堆放在小的下面,然后更新一下就OK

void ins(int x,int y){
	a[y].r=a[x].s; 
	a[x].s=y;
	fa[y]=x;
}

int merge(int x,int y){//这里加return是为了后面的pop
	x=find(x);
	y=find(y);
	if(!x||!y) return x+y;//有一个为0就返回非零那个
	if(x==y) return x;//如果相同就返回其中一个
	if(a[x].x>a[y].x) swap(x,y);
	ins(x,y);
	return x;
}

pop

这里就来了配对堆的核心,也是复杂度的保证,我们假如要弹出堆顶,那么我们首先需要的就是把堆顶的儿子一个一个拿出来合并对吧,但是这样的效率太慢,我们每一次拿出来两个去进行合并,这样的话我们就保证了下一次弹出时的复杂度,这是一种均摊复杂度的思想

void kill(int x){
	int root=find(x);
	a[root].s=a[x].r;
	a[x].r=0;
}

void del(int rr){
	if(!a[rr].s) return 0;//么有儿子就返回0
	int x=a[rr].s;
	int y=a[x].r;
	kill(x);//将这个点拿出来
	if(y) kill(y);
	return merge(merge(x,y),del(rr));
//返回合并后的新的堆顶
}

void pop(int x){
	int root=find(x);
	int r=del(root);
	fa[root]=r;//这个点以及被删掉了,所以收以前所有只想他的店都要指向新的root
	fa[r]=r;//注意这一句,要不然会死循环
	return;
}

push

这个操作也很简单,新加一个点就等于新建一个堆之后把这个和之前的合并

void push(int x,int y){//把x插入y所在的堆里面
	a[x].s=a[x].r=0;
	y=find(y);
	return merge(x,y);
}

top

这个就是返回堆顶的一个操作

int top(int x){
	int root=find(x);
	return a[root].x;
}

模板

#include<bits/stdc++.h>
using namespace std;

int n,m;//堆的个数和操作个数
//这里以小根堆为例
int root;
struct heap{
	int s;//儿子 
	int x;//值
	int r;//左兄弟,这就是他的特色
	 
};//这里加入有很多堆其实不用在意,可以用并查集去维护
heap a[1000010];
int fa[1000010];

int find(int x){
	if(fa[x]==x) return x;
	else return fa[x]=find(fa[x]);
}


void ins(int x,int y){ a[y].r=a[x].s;a[x].s=y;fa[y]=x;}//这就是把y的左兄弟变为x的儿子,之后再把a的大儿子变为y 
void kill(int x) {
	root=find(x);
	a[root].s=a[x].r;
	a[x].r=0;
}//把根节点的儿子变为x的左兄弟,之后再把x的左兄弟变为null  把这个点单独拿出来,之后合并 

int merge(int x,int y){//int 是为了返回新的顶的值,在以后有用 
	if(x==y) return x;//如果是同一个堆的话 就返回一个不管就欧克了 
	if(!x||!y) return x+y;//如果有一个为0  就不用合并了 
	if(a[x].x>a[y].x) swap(x,y);
	ins(x,y);//小的变成堆顶,之后返回堆顶的值 
	return x;
	 
}//合并两个堆 ,相比较两个堆顶的权重,将较大的放在小的下面 

/*void push(int x){
	a[x].s=a[x].r=0;
	root=merge(root,x);
}//插入一个节点,很简单就是建一个堆之后merge
*/ 

bool broken[1000010]; 
int del(int rr){
	if(!a[rr].s) return 0;//如果没有儿子就 return 0
	int x=a[rr].s;//第一个儿子拿出来
	int y=a[x].r;//第二个孩子拿出来
	kill(x);//从这个树里面单独拎出来 
	if(y) kill(y);//如果有y这个点就kill 
	return merge(merge(x,y),del(rr)); //不断的将删除的节点的儿子们给合并,就可以保证之后的均摊复杂度 
}


void pop(int x){
	root=find(x);
	int r=root;
	root=del(root);//将根节点变为合并后的节点
	fa[r]=root;//这个点删除掉了,那么所有指向它的点的父亲都应该变成合并后的那个 
	fa[root]=root;//把堆顶的父亲变为自己 
	broken[r]=true;
	return;
} //弹出堆顶 

int top(int x){
	root=find(x);
	return a[root].x;
}

int main(){
	//freopen("P3377_11.in","r",stdin);
	scanf("%d%d",&n,&m);
	
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i].x);
		a[i].r=a[i].s=0;
		fa[i]=i;
	}
	int op,u,v;
	for(int i=1;i<=m;i++){
		scanf("%d%d",&op,&u);
        if(op==1){ 
            scanf("%d",&v);
            if(!broken[u] && !broken[v])
                merge( find(u),find(v) );
        }
        else if(op==2) {
        	if(broken[u]==true){
        		printf("-1\n");
			}
			else {
				printf("%d\n",top(u));
        		pop(u);
			}
        }
	}
	return 0;
}

小结

这是一个不错的数据结构,可以优化dij,但是好像还要加一个decrease_key的操作,这个以后有时间就去补一下,下次见

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值