N个点M条边的无向连通图,每条边有一个权值,求该图的最小生成树。
输入
第1行:2个数N,M中间用空格分隔,N为点的数量,M为边的数量。(2 <= N <= 1000, 1 <= M <= 50000) 第2 - M + 1行:每行3个数S E W,分别表示M条边的2个顶点及权值。(1 <= S, E <= N,1 <= W <= 10000)
输出
输出最小生成树的所有边的权值之和。
输入示例
9 14 1 2 4 2 3 8 3 4 7 4 5 9 5 6 10 6 7 2 7 8 1 8 9 7 2 8 11 3 9 2 7 9 6 3 6 4 4 6 14 1 8 8
输出示例扫描二维码关注公众号,回复: 3070515 查看本文章37
解题思路:解决这道题可以使用pime肯Kruskal,期间会使用并查集的内容,网上有许多关于prime和Kruskal以及并查集的博客
在这里只介绍prim算法,Kruskal和并查集给出参考博客
参考博客:
并查集参考博客:https://blog.csdn.net/luomingjun12315/article/details/47373345
Kruskal算法参考博客:https://blog.csdn.net/luomingjun12315/article/details/47700237
prime算法详解:
普里姆算法—Prim算法
算法思路:
首先就是从图中的一个起点a开始,把a加入U集合,然后,寻找从与a有关联的边中,权重最小的那条边并且该边的终点b在顶点集合:(V-U)中,我们也把b加入到集合U中,并且输出边(a,b)的信息,这样我们的集合U就有:{a,b},然后,我们寻找与a关联和b关联的边中,权重最小的那条边并且该边的终点在集合:(V-U)中,我们把c加入到集合U中,并且输出对应的那条边的信息,这样我们的集合U就有:{a,b,c}这三个元素了,一次类推,直到所有顶点都加入到了集合U。
下面我们对下面这幅图求其最小生成树:
假设我们从顶点v1开始,所以我们可以发现(v1,v3)边的权重最小,所以第一个输出的边就是:v1—v3=1:
然后,我们要从v1和v3作为起点的边中寻找权重最小的边,首先了(v1,v3)已经访问过了,所以我们从其他边中寻找,发现(v3,v6)这条边最小,所以输出边就是:v3—-v6=4
然后,我们要从v1、v3、v6这三个点相关联的边中寻找一条权重最小的边,我们可以发现边(v6,v4)权重最小,所以输出边就是:v6—-v4=2.
然后,我们就从v1、v3、v6、v4这四个顶点相关联的边中寻找权重最小的边,发现边(v3,v2)的权重最小,所以输出边:v3—–v2=5
然后,我们就从v1、v3、v6、v4,v2这2五个顶点相关联的边中寻找权重最小的边,发现边(v2,v5)的权重最小,所以输出边:v2—–v5=3
最后,我们发现六个点都已经加入到集合U了,我们的最小生成树建立完成。
AC代码:
一:kruskal代码:
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
const int maxn=5e4+10;
struct node{
int a,b,c;
}tre[maxn];
int rank[maxn],vis[maxn];
bool cmp(node a,node b)
{
return a.c<b.c;//按权值从小到大排序
}
void init(int n)
{
for(int i=0;i<=n;i++)
{
vis[i]=i;
rank[i]=0;//数的高度
}
}
int findx(int x)
{
int root=(x==vis[x]?x:findx(vis[x]));
while(x!=root)//优化,将同一树的节点都指向根节点
{
int t=vis[x];
vis[x]=root;
x=t;
}
return root;
}
void merge(int x,int y)
{
int fx=findx(x);//寻找根节点
int fy=findx(y);
if(rank[fx]<rank[fy])
{
vis[fx]=fy;
}
else
{
vis[fy]=fx;
if(rank[fx]=rank[fy]) rank[fx]++;
}
}
//n为边的数量,m为顶点的数量
int kruskal(int n,int m)
{
int nedge=0,res=0;
for(int i=1;i<=n&&nedge!=m-1;i++)
{
if(findx(tre[i].a)!=findx(tre[i].b))
{
merge(tre[i].a,tre[i].b);
res+=tre[i].c;
nedge++;
}
}
// if(nedge<m-1)
// return -1;
// else
return res;
}
int main()
{
int n,m;
cin>>n>>m;
init(n);
for(int i=1;i<=m;i++)
{
cin>>tre[i].a>>tre[i].b>>tre[i].c;
}
sort(tre+1,tre+m+1,cmp);
cout<<kruskal(m,n)<<endl;
return 0;
}
prim解法代码:
#include<iostream>
#include<cstring>
#define mem(a) memset(a,0,sizeof(a))
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=1e3+10;
int cost[maxn][maxn];//记录两点之间的距离,不存在标记为无穷大
int mincost[maxn];//从x集合出发到每个顶点的最小值
bool used[maxn];//判断当前节点是否在集合中
int n,m;
int prim()
{
memset(mincost,INF,sizeof(mincost));
mem(used);
mincost[1]=0;//赋初值
int ans=0;
while(true)
{
int v=-1;
for(int u=1;u<=n;u++)
if(!used[u]&&(v==-1||mincost[u]<mincost[v]))
v=u;
if(v==-1)
break;//条件成立说明所有的点都已经加入
used[v]=true;//标记点v已经取过
ans+=mincost[v]; //将边的长度加进去;
//然后不断的更新mincost数组,从x集合出发到每个顶点的最小值,x集合表示还没有取过的点,
//显然mincost[v]没有取过的点到每一个取过的点的最小距离(仔细阅读prim算法思想)
for(int u=1;u<=n;u++)
{
if(!used[u])
mincost[u]=min(mincost[u],cost[v][u]);
}
}
return ans;
}
int main()
{
cin>>n>>m;
memset(cost,INF,sizeof(cost));
int x,y,z;
for(int i=0;i<m;i++)
{
cin>>x>>y>>z;
cost[x][y]=z;
cost[y][x]=z;
}
int k=prim();
cout<<k<<endl;
return 0;
}