题意
使用prim算法找出包含所有点的却只有n-1条边的无向连通子图, 并求出子图的最小权值。
分析
此题是非常裸的prim算法题,直接套用模板就ok了,但是一定要理解思想是怎样的。
思想
将图分为两个集合,其中一个集合是最小生成树(一开始是空集),另一个集合是不属于最小生成树的点集S,每次都找离最小生成树最近的点,并把这个点加入最小生成树,然后利用刚增加的点去更新 其它点到最小生成树的距离
这里定义一个数组d,d[i]的含义是从点i到集合S的最短距离
解决方案:
1.输入点数和边数,初始化邻接矩阵和距离数组(定义为某个点到最小生成树集合的最短距离),输入边权(如果有重边,则取边权最小的边存储)
2.进行n次操作,每次操作都会找到一个距离集合S最近的点,如果在某一次操作中找不到这个点,则说明这个图不连通,则这个图没有最小生成树
3.随机抽取一个点(一般默认1号点),以这个点作为集合的第一个点,则这个点到集合的距离为0
4.每次找到一个新点加入集合S,就用这个新点去更新其它点到集合S的距离
代码
#include <iostream>
#include <cstring>
using namespace std;
const int N = 510, INF = 0x3f3f3f3f;
//d数组定义为某个点到集合S的最短距离,g数组是邻接矩阵,n是点数,m是边数,ans是最小生成树的所有边权之和
int d[N], g[N][N], n, m, ans;
//st数组定义为某个点到集合S的最短距离是否已经确定,如果确定了,则为true
bool st[N];
void prim() {
memset(d, INF, sizeof d); //一开始d[1]也是无穷大
//遍历n次,每一次操作都会找到一个点加入集合S(如果图是连通的)
for (int i = 0; i < n; i++) {
int t = -1;
for (int j = 1; j <= n; j++)
if (!st[j] && (t == -1 || d[j] < d[t]))
t = j;
//因为第一次操作时所有点到集合S的最短距离都是无穷大,所以第一次操作就不需要判断是否为无穷大
if (i && d[t] == INF) {
puts("impossible");
return;
}
//将新点标记为已经找到最短距离
st[t] = 1;
//第一次操作不需要记录边权
if (i) ans += d[t];
//每次找到一个符合条件的新点,就用这个点更新集合S到其它点的最短距离
for (int j = 1; j <= n; j++)
d[j] = min(d[j], g[t][j]);
}
cout << ans;
}
int main() {
memset(g, INF, sizeof g);
cin >> 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); //输入边权,如果有重边则选择边权最小的边
}
prim();
return 0;
}
总结
感觉prim算法本质就是每次去找两个集合之间的最短距离