题目描述
给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环,边权可能为负数。
求最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible。
给定一张边带权的无向图 G=(V,E),其中 V 表示图中点的集合,E 表示图中边的集合,n=|V|,m=|E|。
由 V 中的全部 n 个顶点和 E 中 n−1 条边构成的无向连通子图被称为 G 的一棵生成树,其中边的权值之和最小的生成树被称为无向图 G 的最小生成树。
输入格式
第一行包含两个整数 n 和 m。
接下来 m 行,每行包含三个整数 u,v,w,表示点 u 和点 v 之间存在一条权值为 w 的边。
输出格式
共一行,若存在最小生成树,则输出一个整数,表示最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible。
数据范围
1≤n≤500,
1≤m≤105,
图中涉及边的边权的绝对值均不超过 10000。
样例
输入样例:
4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4
输出样例:
6
Prim算法的思路是:从一个根结点开始,让一棵小树慢慢长大,也就是说,它是一个结点一个结点地增加的。
先在图里面随便选一个结点作为根结点(这个先选的点习惯上是编号为1的结点),然后我们需要找一条边,这条边必须是与这棵数相连且长度最小的边。
如图,第一次寻找后,与根结点相连的长度最小的边是1。
然后以v1,v4为基础,继续向外扩张,寻找与v1,v4构成的树相连的长度最小的边,我们发现有两条最小的边长度都为2,我们任选一条即可,这里选择上面的这条,如图所示
然后以v1,v2,v4为树,寻找下一个与树相连的且长度最小的边,发现有一条长度为2的边是最小的,将其加入树中,如图所示
然后以v1,v2,v3,v4为树,继续寻找下一条与树相连且长度最小的边,此时我们发现有一条长度为3的边,但是我们不能将其收入树中,因为收入该条边后会构成回路,而树的定义是无回路,所以我们找到一条长度为4与树相连且连接后不够成回路的边,如图所示:
而将v7收入树中后,显然与v7相连的最短边为1,收入后不构成回路,所以直接将其收入树中,如下图:
最后收入的顶点是v5,最短边是6,如下图
到此为止,我们已将所有的顶点都收入了,这时就构成了一棵最小生成树。
下面说下代码的写法:
步骤:
1.dist[i] = 0x3f3f3f3f
2.循环迭代n次,每次寻找集合外距离集合最近的点
3.用t更新其他点到集合的距离
4.将t加入集合。
prim算法的时间复杂度是$O(n^2)$,适用于稠密图,所以下面也用邻接矩阵来存。
prim与dijkstra算法的比较:
dijkstra更新的是当前结点到起点的距离dist[j] = min(dist[j], dist[t] + g[t][j]);
prim算法更新的是当前结点到集合的距离dist[j] = min(dist[j], g[t][j]);
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 510, INF = 0x3f3f3f3f;
int n,m;
int dist[N]; //存集合与点的距离
int g[N][N]; //用邻接矩阵来存图
bool st[N]; //标记某个点是否在集合中
int prim()
{
//1.dist[t] = 0x3f3f3f3f
memset(dist, 0x3f, sizeof dist);
int res = 0; //res用来存最小生成树的权值
for(int i = 0; i < n; i ++ )
{
//2.每次找一个距离当前集合距离最小的点
/*这里t = -1是为了方便初始化,因为在一开始我们要先选一个点,这个点其实没什么要求,习惯上我们选编号为1的结点
下面的if条件中写了如果t == -1就更新t,t也可以写其他负值,如-2,-3等,只需要把下面if中也写成t == 自己设置的负值即可*/
int t = -1;
for(int j = 1; j <= n; j ++ )
if(!st[j] && (t == -1 || dist[t] > dist[j])) //这里&&后面的要加括号,注意优先级
t = j;
/*这里很巧妙,因为i是从0开始的,所以可以直接用if(i)来判断是不是第一个点
第一个点不用向res中加边,因为它没有边,只有一个孤零零的点,其次,如果某个点不是第一个点且其到集合的距离为无穷
那就说明图不连通*/
if(i && dist[t] == INF) return INF; //如果不是第一个点,且到集合的距离为无穷,则说明图不连通,返回INF
if(i) res += dist[t]; /*此时经过上面if,走到这里一定是连通图,如果不是第一个节点,则将该结点到集合的距离加入到
最小生成树的权值中去*/
//3.然后用t更新其他点到集合的距离
for(int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], g[t][j]);
//4.将这个结点加入到集合中去
st[t] = true;
}
return res;
}
int main()
{
memset(g, 0x3f,sizeof g); //初始化邻接矩阵,存的是每个两点之间的距离,先初始化为较大的数
scanf("%d%d",&n,&m);
while(m --)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
g[a][b] = g[b][a] = min(g[a][b], c); //处理重边
}
int t = prim();
if(t == INF) puts("impossible"); //图不连通,不存在最小生成树
else printf("%d\n", t);
return 0;
}