最小生成树算法——Prim算法和Kruskal算法

本文详细解析了Prim和Kruskal两种最小生成树算法的原理与实现,包括Prim算法的“加点法”和Kruskal算法的贪心思想及并查集应用,并提供了代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Prim算法理解,可以看这篇文章https://blog.youkuaiyun.com/yeruby/article/details/38615045

其实就是每次从当前树中外选取一个离树最近且不构成环的点,同时sum记录权值,然后把这个点加入树中,直到所有节点都被访问过,最小生成树生成成功,输出最小生成树的权值和。

下面是Prim算法的板子,和最短路有点相似,也称为“加点法”。

//最小生成树Prim算法 
#include<iostream>
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 110;
int n,m;
int G[maxn][maxn];
int prim()
{
	int mst[maxn],sum=0,lowcost[maxn];
	//lowcost[i]: 表示以i为终点的边的最小权值,
	//当lowcost[i]=0说明以i为终点的边的最小权值=0,
	//也就是表示i点加入了MST
    //mst[i]:表示对应lowcost[i]的起点,
	//即说明边<mst[i],i>是MST的一条边,
	//当mst[i]=0表示起点i加入MST  
	for(int i = 2;i<=n;i++){
		lowcost[i]=G[1][i];
		mst[i]=1;//初始化lowcost数组为到起始点的距离,mst数组为1(即所有点的起点都是1) 
	}
	mst[1]=0;//起始点进树 
	for(int  i = 2;i<=n;i++){
		int minn=INF,minid=0;
		for(int j = 2;j<=n;j++)//遍历lowcost数组,查询最小值 
			if(minn>lowcost[j]&&lowcost[j]!=0)
			   minn=lowcost[minid=j];
//		printf("%d-%d=%d\n",mst[minid],minid,minn); 
		lowcost[minid]=0;//查询到的最短的点进树 
		sum+=minn;
		for(int j=2;j<=n;j++){//节点进树后更新外面的点到树的最短距离并且mst记录其起点 
			if(G[minid][j]<lowcost[j]){
				 lowcost[j]=G[minid][j];
				 mst[j]=minid;
			}  
		}
	}
	return sum;
} 
int main()
{
	scanf("%d %d",&n,&m);
	for(int i = 1;i<=n;i++)
	   for(int j = 1;j<=n;j++)
	       G[i][j]=INF;       //初始化 
	for(int i = 1;i<=m;i++){
		int a,b,cost;
		scanf("%d %d %d",&a,&b,&cost);
		G[a][b]=G[b][a]=cost;
	}
	int cost=prim();
	printf("%d\n",cost);
	return 0;
 } 

其实也可以用一个visited数组来标记是否被访问过。

Kruskal算法(基于贪心思想和并查集的实现)

关于算法的理解可以看这篇博客https://blog.youkuaiyun.com/luoshixian099/article/details/51908175

不过上面博客的代码实现比较复杂。

关于并查集的理解可以看这篇文章https://blog.youkuaiyun.com/niushuai666/article/details/6662911

其实算法很好理解,就是先把图中所有边按照权值从小到大给他们排个序(贪心),然后每次取一条边,判断边的起点和终点是否在一个连通分量里面,如果不在就合并,(这里关键在于连通分量的查询和合并),直到构成一棵最小生成树。

一谈到贪心,我们就不能简单的去用,还要能证明,不然说不定就错了(QAQ,就像01背包一样)

具体的证明过程可以去百度,或者看紫书356页。

下面给出伪代码的实现

把所有边排序,记第i小的边为e[i](i<=1<m)
初始化MST为空
初始化连通分量为空,让每个点自成一个独立的连通分量
初始化sum=0
for(i=0;i<m(边的个数);i++)
    if(e[i].u(起点)和e[i].v(终点)不在同一个连通分量中){
         把边e[i]加入MST中
         合并e[i].u和e[i].v所在的连通分量
         sum+=e[i].w;(记录权值)
    }


 

下面是代码具体实现,不过涉及到间接排序(不知道的可以百度,有点绕),其实也可以开个结构数组排序。

#include <iostream>
#include<algorithm>
using namespace std;
int p[110],w[110],r[110],u[110],v[110];
//第i条边的两个端点序号和权值分别存储在u[i],v[i]和w[i]中
//排序后第i小的序号存储在r[i]中(间接排序,排序的是对象的代号而不是其本身) 
int n,m;
bool cmp(const int i ,const int j)//间接排序函数 
{
	return w[i]<w[j];
} 
int find(int x)//并查集的find 
{
	return p[x]==x?x:p[x]=find(p[x]);//路径压缩 
} 
void init()//初始化并查集和边的序号 
{
	for(int i = 1;i<=n;i++)
	   p[i]=i;
	for(int i = 1;i<=m;i++)
	   r[i]=i;
}
int Kruskal()
{
	int ans=0;
	init();
	sort(r,r+m,cmp);//给边排序 
	for(int i = 1;i<=m;i++){
		int e=r[i];
		int x=find(u[e]);
		int y=find(v[e]);//找出两个边所在集合的编号 
		if(x!=y){
			ans+=w[e];
			p[x]=y;
			//如果在不同集合,合并 
		}
	}
	return ans;
}
int main(int argc, char** argv) {
	while(scanf("%d %d",&n,&m)!=EOF){
		for(int i = 0;i<m;i++)
			scanf("%d %d %d",&u[i],&v[i],&w[i]);
		printf("%d\n",Kruskal());
	}
	return 0;
}

然后就是各种习题的练习,下面给出一些经典的题和题解。

POJ 1251Jungle Roads —— POJ1251题解

POJ 1287 Networking —— POJ1287题解

POJ 2031 Building a Space Station——POJ2031题解

POJ 2421 Constructing Roads —— POJ2421题解

ZOJ1586QS Network —— ZOJ1586题解

POJ1789Truck History —— POJ1789题解

POJ2349Arctic Network——POJ2349题解

POJ1751Highways——POJ1751题解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值