图的MST(最小生成树算法)Kruskral+Prim

##前言


文章记录数据结构中求最小生成树的两种经典算法:Kruskal和Prim算法。

##Kruskal算法


算法的实现步骤:

个人理解:

  1. 将图中所有的带权边按照权值大小(从小到大的次序)依次排序;形如<2,5>:3;<2,3>:4等,表示结点2到结点5能够连通,路径长度为3。
  2. 从上按照从小到大的‘顶点边权列表’中依次选择边权值最小且不构成环路的这些边形成最小生成树。

老师给的步骤:

  1. 将所有的边按权值排序;
  2. 设每个顶点为一个独立的点集,生成树T为空集;
  3. 依序扫描每一条边,直到已输出n-1条边: 若vi、vj均不在同一点集中,则将该边加入生成树T中,并合并这两个点集;否则舍弃该边;

图示辅助理解:
这里写图片描述

算法的代码实现:

#include<iostream>
#include<algorithm>
#define maxv 101
#define maxe 100
using namespace std;
int r[maxe];
int p[maxv];
int u[maxv],v[maxv],w[maxv];
int n,m;
int cmp(const int i,const int j)
{
	return w[i]<w[j];
}
int find(int x)
{
	return p[x]==x?x:p[x]=find(p[x]);
}
int Kruskal(int u[maxv],int v[maxv],int w[maxv],int n,int m)
{
	int ans=0;
	//循环设置每一个顶点序号 
	for(int i=0;i<n;i++)
		p[i]=i;
	for(int i=0;i<m;i++)
		r[i]=i;	
	sort(r,r+m,cmp);
	/*
	cmp 为sort函数中的排序规则
	其中的常量 i j 表示待排序中的两个变量
	return 后的内容表示排序的规则
	w[i]<w[j] 按照 w 权值的大小进行排序
	比较结果为真返回 1 否则返回 0 
	*/
	//排序后 边权 
	//for(int i=0;i<m;i++)
	//	cout<<"第"<<r[i]<<"条边的权值为:"<<w[r[i]]<<" "<<endl;
	for(int i=0;i<m;i++)
	{
		int e=r[i];
		//x y 分别定义为  e 这条边的起点和终点
		//利用递归来判定该边能达到联通终点是否与起点有环 
		int x=find(u[e]);
		int y=find(v[e]);
		if(x!=y)
		{
			ans+=w[e];
			cout<<"第"<<e<<"条边:权值为:"<<w[e]<<"; 从" <<u[e]<<"该起点到"<<v[e]<<"该终点"<<endl; 
			p[x]=y; 
		}
	}
	return ans; 
}
int main()
{
	int n,m,ans;
	while(cin>>n>>m)
	{
		for(int i=0;i<m;i++)
			cin>>u[i]>>v[i]>>w[i];
		ans=Kruskal(u,v,w,n,m);
		cout<<"最小生成树权值最小是:"<<ans<<endl;
	}
	return 0;
}
/*测试数据
7 12
2 5 3
2 3 4
3 5 5
1 4 7
0 2 9
0 3 10
1 6 12
5 6 32
3 6 23
0 1 22
4 6 20
1 3 15
*/ 

运行截图:

这里写图片描述

##Prim算法


算法的实现步骤:

  1. E1:任取一个顶点构成U={v0};构造向量cost[0…n-1]和adj[0…n-1],cost[i]表示顶点vi到U的最短边的长度,adj[i]表示顶点vi到U的最短边在U中的邻接点的下标;其中,vi∈V-U。初始时,生成树T为空集。
  2. E2:重复n-1次
  3. E21:从V-U中选出cost值最小的顶点vk,将边(vk, vadj[k])加入到生成树T中,然后将vk并入U中;
  4. E22:修正V-U中各顶点的cost值和adj值;

图示辅助理解
这里写图片描述

算法的代码实现:

#include<iostream>
#define maxv 101
#define maxe 100
#define INF 1000000
using namespace std;
void Prim(int n ,int arc[][101])
{
	//定义 ans 为最小生成树最小权值 
	int cost[maxv],closet[maxv],ans=0;
	closet[0]=0;//从 V-U 到 U 连接最短边对应的邻接点序号 
	cost[0]=0;//将 0元素加入到 U集合中 设置标志数组 
	for(int i=1;i<n;i++)
	{
		//初始仅有 0 加入 U cost和closet依次初始化 
		cost[i]=arc[0][i];
		closet[i]=0;
	}
	for(int i=1;i<n;i++)
	{
		//从V-U中取出一个元素加入到 U中 共执行 n-1次 
		int min=INF,k=0;
		for(int j=1;j<n;j++)
		{
			//if条件为判定该结点是否在 U中 且是否为连接 U的权值最小 
			if(cost[j]&&cost[j]<min)
			{
				min=cost[j];//更新最小值 
				k=j;//记录连接 U和 V-U的接点
			}
		}
		//更新 MST权值和 
		ans+=min;
		//将该点加入U集合中 
		cost[k]=0;
		for(int j=1;j<n;j++)
		{
			//更新cost 和closet值 
			if(cost[j]&&cost[j]>arc[k][j])
			{
				cost[j]=arc[k][j];
				closet[j]=k;
			}
		}
	}
	//输出 最小生成树 权值最小的值 
	cout<<ans<<endl;
}
int main()
{
	int n,arc[maxv][maxv];
	cin>>n;
	//输入图邻接矩阵 
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++)
			cin>>arc[i][j];
	//调用Prim算法 
	Prim(n,arc);
	return 0;
}

##Prim和Kruskal算法的区别和联系


Prim算法是加入一个顶点到集合U中比较与U中的点相连结点的边的长度,适合用于稠密图即顶点边比较多的情况。
Kruskla算法是将所有的边权和顶点依次罗列排序,进行依次比较,得出最小生成树,比较适合于稀疏图即顶点边比较少的情况。
Prim算法和Kruskal算法均属于贪心算法。贪心算法一般上是在当前情况下求取最优解,可能无法得出‘全局最优解’,会陷入‘局部最优解’的可能。但对于最小生成树来说,Prim和Kruskal算法均得到是最优解。有具体的数学论证。
Prim的算法效率E+VlogV,Kruskal的算法效率ElogE。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值