8.2.1再谈最小生成树//用堆

#include<iostream>
using namespace std;
int dis[7],book[7]={0};//book数组用来记录哪些顶点已经放入生成树中 
int h[7],pos[7],size;//h用来保存堆,pos用来存储每个顶点在堆中的位置,size为堆的大小 

//交换函数,用来交换堆中的两个元素的值 
void swap(int x,int y){
	int t;
	t=h[x];
	h[x]=h[y];
	h[y]=t;
	
	//同步更新pos
	t=pos[h[x]];
	pos[h[x]]=pos[h[y]];
	pos[h[y]]=t; 
} 

//向下调整函数 
void siftdown(int i){
	//传入一个需要向下调整的结点编号i,这里传入1,即从堆的顶点开始向下调整 
	int t,flag=0;//flag用来标记是否需要*继续*向下调整 
	//当i结点有儿子(其实是至少有左儿子)并且有需要继续调整的时候循环就执行 
	while(i*2<=size&&flag==0){
		//首先比较i和它左儿子i*2在dis中的值,并用t记录值较小的结点编号 
		if(dis[h[i]]>dis[h[i*2]]){
			t=i*2;
		}
		else
			t=i;
		//如果它有右儿子,再对右儿子进行讨论 
		//由于堆是完全二叉树,所以堆中任意一个结点的右孩子(存在的情况下)编号均满足i*2+1<=size 
		if(i*2+1<=size){
			//如果右儿子的值更小,更新较小的结点编号 
			if(dis[h[t]]>dis[h[i*2+1]]){
				t=i*2+1;
			}
		}	
		//如果发现最小的结点编号不是自己,说明子结点中有比父结点更小的 
		if(t!=i){
			swap(t,i);//交换它们 
			i=t;//更新i为刚才与它交换的儿子结点的编号,便于接下来继续向下调整 
		}
		else
			flag=1;//否则说明当前的父结点已经比两个子结点都要小了,不需要再进行调整了 
	
	}
}

//向上调整函数
void siftup(int i){
	//传入一个需要向上调整的节点编号i 
	int flag=0;//用来标记是否需要继续向上调整 
	if(i==1)	
		return;
	//如果是堆顶,就返回,不需要调整了 
	while(i!=1&&flag==0){
		//不在堆顶,并且当前结点i的值比父结点小的时候就继续向上调整 
		if(dis[h[i]]<dis[h[i/2]]){
			swap(i,i/2);//交换它和它爸爸的位置 
		}
		else
			flag=1;//表示已经不需要调整了,当前结点的值比父结点的值要大 
		i=i/2;//这句话很重要,它更新了编号i为它父结点的编号,从而便于下一次继续向上调整 
	}
} 

//从堆顶取出一个元素
int pop(){
	int t;
	t=h[1];//用一个临时变量记录堆顶点的值
	pos[t]=0;//其实这句话要不要无所谓
	h[1]=h[size];//将堆的最后一个点赋值到堆顶
	pos[h[1]]=1;
	size--;//堆的元素减少1
	siftdown(1);//向下调整
	return t;//返回之前记录的堆顶点 
} 

int main(){
	int n,m,i,j,k;
	int u[19],v[19],w[19];//u,v,w和next数组大小要根据实际情况来设置,此图是无向图,要比2*m的最大值大1 
	int first[7],next[19];
	//first数组表示各个顶点的第一条边的编号(重点就是它只是一个编号)
	//next数组表示"编号为i的边"的"下一条边"的编号(重点就是它只是一个编号) 
	//first要比n的最大值大1,要比2*m的最大值大1  
	
	int inf=99999999;//用inf(infinity的缩写)存储一个我们认为的无穷大值 
	int count=0,sum=0;//count用来记录生成树中顶点的个数,sum用来存储路径之和
	
	//读入n和m,n表示顶点个数,m表示边的条数
	cout<<"请分别输入原图顶点个数n,边的条数m:"<<endl;
	cin>>n>>m;

	//开始读入边 
	cout<<"请依次输入m条边,每条边起点为b1,终点为b2,权重为z3。即b1->b2(z3):"<<endl; 
	for(i=1;i<=m;i++){
		//读入每一条边 
		cin>>u[i]>>v[i]>>w[i];		
	}
	
	//这里是无向图,所以需要将所有的边反向再存储一遍 
	for(i=m+1;i<=2*m;i++){
		u[i]=v[i-m];
		v[i]=u[i-m];
		w[i]=w[i-m];	
	}
	
	//开始使用邻接表存储边 
	for(i=1;i<=n;i++){
		first[i]=-1;
	}
	for(i=1;i<=2*m;i++){
		next[i]=first[u[i]];
		first[u[i]]=i;
	}

	//Prim核心部分开始
	//将1号顶点加入生成树
	book[1]=1;//这里用book来标记一个顶点已经加入生成树
	count++;
	
	//初始化dis数组,这里是1号顶点到其余各个顶点的初始距离 
	dis[1]=0;
	for(i=2;i<=n;i++){
		dis[i]=inf;
	}
	k=first[1];
	while(k!=-1){
		dis[v[k]]=w[k];
		k=next[k];
	}
	
	//初始化堆
	size=n;
	for(i=1;i<=size;i++){
		h[i]=i;
		pos[i]=i;
	}
	for(i=size/2;i>=1;i--){
		siftdown(i);//从最后一个非叶子结点到底1个结点一次进行向下调整
	}
	pop();//先弹出一个堆顶元素,因为此时堆顶是1号顶点
	
	while(count<n){
		j=pop();
		book[j]=1;
		count++;
		sum+=dis[j];
		
		//扫描当前顶点j所有的边,再以j为中间结点,进行松弛
		k=first[j];
		while(k!=-1){
			if(book[v[k]]==0&&dis[v[k]]>w[k]){
				dis[v[k]]=w[k];//更新距离
				siftup(pos[v[k]]);//对该点在堆中进行向上调整
				//提示:pos[v[k]]存储的是顶点v[k]再堆中的位置 
			}
			k=next[k];
		} 
	} 
	
	cout<<"该图的最小生成树的边的总长度之和最短为: "<<sum;//打印结果
	getchar();
	return 0; 
	
}

/*
使用堆优化思路:
1、需要用到3个数组
2、数组dis用来记录生成树到各个顶点的距离 
3、数组h是一个最小堆,堆里面存储的是顶点编号。注意:这里并不是按照顶点编号的大小来建立最小堆,
而是按照顶点在数组dis中所对应的值来建立这个最小堆 
4、数组pos用来记录每个顶点在最小堆中的位置
5、借助堆,每次选边的时间复杂度是O(logM),然后使用邻接表来存储图的话,整个算法的时间复杂度会降
低到O(MlogN)
*/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值