最小生成树(二)--prim算法实现以及堆优化

7 篇文章 0 订阅

一、最小生成树---prim算法实现

思想:

1、从任意一个顶点开始构造生成树,假设就从1号顶点吧, 首先将顶点1加入生成树中,用一个一维数组book来标记 哪些顶点已经加入了生成树。
 2、用数组dis记录生成树到各个顶点的距离,最初生成树中之后1号 顶点,有直连边时,数组dis中存储的就是1号顶点到该顶点 的边的权值,没有直连边的时候就是无穷大,即初始化dis数组。
 3、从数组dis中选出离生成树最近的顶点(假设这个顶点为j) 加入到生成树中(即在数组dis中找到最小值)。再以j为中间点, 更新生成树到每一个非树顶点的距离(就是松弛啦), 即如果dis[k]>e[j][k]则更新dis[k]=e[j][k]。
 4、重复第三步,直到生成树中有n个顶点为止。
 

代码实现:

<span style="font-size:18px;">#include<stdio.h>
int main(void)
{
	int n,m,i,j;
	int k,min;
	int t1,t2,t3;
	int e[7][7],dis[7];
	int book[7]={0};//book数组初始化 
	int inf=99999999;
	int count=0,sum=0;
	//count用来记录生成树中顶点的个数,sum用来存储路径之和 
	scanf("%d %d",&n,&m);
	//读入n,m,n表示顶点的个数,m表示边的条数。 
	//初始化 
	for(i=1;i<=n;i++)
	{
		for(j=1;j<=n;j++)
		{
			if(i==j)
			{
				e[i][j]=0;
			}
			else
			{
				e[i][j]=inf;
			}
		}
	}
	//开始读入边 
	for(i=1;i<=m;i++)
	{
		scanf("%d%d%d",&t1,&t2,&t3);
		//注意无向图,需要将边反向在存储一遍 
		e[t1][t2]=t3;
		e[t2][t1]=t3;
	}
	//初始化dis数组,这里是1号顶点到各个顶点的初始距离,因为当前生成树中只有1号顶点	
	for(i=1;i<=n;i++)
	{
		dis[i]=e[1][i];
	}
	//将1号顶点加入生成树,这里book来标记一个顶点是否已经加入生成树 
	book[1]=1;
	count++;
	while(count<n)
	{
		min=inf;
		for(i=1;i<=n;i++)
		{
			if(book[i]==0&&dis[i]<min)
			{
				min=dis[i];
				j=i;
			}
		}
		book[j]=1;
		count++;
		sum=sum+dis[j];
		//扫描当前顶点j所有的边,再以j为中间点,更新生成树到每一个非树顶点的距离; 
		for(k=1;k<=n;k++)
		{
			if(book[k]==0&&dis[k]>e[j][k])
			{
				dis[k]=e[j][k];
			} 
		}
	}
	printf("%d\n",sum);
	return 0;	
} 
</span>

二、最小生成树—prim算法的堆优化

需要用到三个数组
1、数组dis用来记录生成树到各个顶点的距离
2、数组h是一个最小堆,堆里面存储的是顶点编号。(请注意,这里并不是按照顶点编号的大小来建立最小堆的,而是按照顶点在数组dis中所对应的值来建立这个最小堆的)
3、pos数组来记录每个顶点在最小堆中的位置


例如,下图中
下面最小堆中的圆圈中存储的是顶点编号,圆圈右下角的数是该顶点(圆圈里面的数)到生成树的最短距离,即数组dis中存储的值 



代码实现:

<span style="font-size:18px;">#include<stdio.h>
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)
{
	int t,flag=0;
	//flag用来标记是否需要继续向下调整 
	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;
		}
		//如果它有右儿子,再对右儿子进行讨论 
		if(t*2+1<=size)
		{
			//如果右儿子的值更小,更新较小的结点编号 
			if(dis[h[t]]>dis[h[i*2+1]])
			{
				t=i*2+1;
			}
		}
		//如果发现最小的结点编号不是自己,说明子结点中有比父结点更小的 
		if(t!=i)
		{
			//交换它们 
			swap(t,i);
			//更新i为刚才与它交换的儿子结点的编号,便于接下来继续向下调整 
			i=t;
		}
		else
		{
			//否则说明当前的父结点已经比两个子结点都要小了,不需要再进行调整了 
			flag=1;
		}
	}
}
//传入一个需要向上调整的结点编号i 
void siftup(int i)
{
	int flag=0;
	//flag用来标记是否需要继续向上调整 
	if(i==1)
	{
		//如果是堆顶,就返回,不需要调整 
		return ;
	}
	//不在堆顶,并且当前结点i的值比父结点小的时候继续向上调整 
	while(i!=1&&flag==0)
	{
		//判断是否比父结点的小 
		if(dis[h[i]]<dis[h[i/2]])
		{
			//交换它和它父亲的位置 
			swap(i,i/2);
		}
		else
		{
			//表示已经不需要调整了,当前结点的值比父结点的值要大 
			flag=1;
		}
		//更新编号i为它父结点的编号,从而便于下一次继续向上调整 
		i=i/2;
	}
}
//从堆顶取出一个元素 
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(void)
{
	int n,m,i,j,k;
	//u,v,w和next的数组大小要根据实际情况来设置,此图是无向图,要比2*m的最大值要大1 
	//first要比n的最大值要大1,要比2*m的最大值要大1 
	int u[19],v[19],w[19],first[7],next[19];
	int inf=99999999;
	//count用来记录生成树中顶点的个数,sum用来记录存储路径之和 
	int count=0,sum=0;
	//读入n和m,n表示顶点个数,m表示边的条数 
	scanf("%d %d",&n,&m);
	//开始读入边 
	for(i=1;i<=m;i++)
	{
		scanf("%d%d%d",&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;
	}
	//将1号顶点加入生成树,book数组来标记一个顶点是否已经加入生成树 
	book[1]=1;
	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();
	while(count<n)
	{
		j=pop();
		book[j]=1;
		count++;
		sum=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];
				//对该点在堆中进行向上调整
				//pos[v[k]]存储的是顶点v[k]在堆中的位置 
				siftup(pos[v[k]]); 
			}
			k=next[k];
		}
	}
	printf("%d\n",sum);
	return 0;
}</span>



  • 6
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值