问题描述
给定一个n个点m条边的无向图,图中可能存在重边和自环,边权可能为负数。
求最小生成树的树边权重之和,如果最小生成树不存在则输出impossible。
给定一张边带权的无向图G=(V, E),其中V表示图中点的集合,E表示图中边的集合,n=|V|,m=|E|。
由V中的全部n个顶点和E中n-1条边构成的无向连通子图被称为G的一棵生成树,其中边的权值之和最小的生成树被称为无向图G的最小生成树。
切分定理
切分:把图中的结点分成两部分,称为一个切分(cut)。
横切边:如果一个边的两个端点,属于切分不同的两边,
这个边称为横切边(crossing edge)。
切分定理:给定任意切分,横切边中权值最小的边必然属于最小生成树。
算法思想
朴素Prim算法,适用于稠密图,算法时间复杂度为 O ( n 2 ) O(n^2) O(n2)。
遍历所有结点,找到距离集合
S
S
S中某一点最近的且不在集合
S
S
S中的点,将该点加入集合
S
S
S,并且使用该点更新其它点到集合
S
S
S的最短距离。
这里的集合
S
S
S表示的是生成树,在集合中,表示已经包含在生成树中。
点到集合s的最短距离表示该点到集合中某点的最短距离。
时间复杂度
算法要将所有点加入到集合 S S S中,需要循环 n n n次。在每次循环中,要找到集合 S S S距离最近的点进行更新,还需要遍历一遍所有点。所以时间复杂度为 O ( n 2 ) O(n^2) O(n2),
代码实现
#include <iostream>
#include <cstring>
using namespace std;
const int N = 510, INF = 0x3f3f3f3f;
int n, m;
//st[i]表示点i到集合s的最短距离是否已确定
//dis[i]表示点i到集合的最短距离
int g[N][N], st[N], dis[N];
int prim()
{
memset(dis, 0x3f, sizeof dis);
dis[1] = 0;
int res = 0; //最小生成树的树边权重之和
//循环n次,每次找到一个点,它不在集合中且到集合中其它点距离最短
for(int i = 1; i <= n; i ++)
{
int t = -1;
//查找距离集合s中某一点最近的且不在集合s中的点t
for(int j = 1; j <=n; j ++){
//点j不在集合s中,
if(!st[j] && (t == -1 || dis[t] > dis[j])) t = j;
}
//如果不是第一个点并且点t到集合的最短距离为无穷大
//则说明图是不连通的,不存在最小生成树
if(dis[t] == INF) return INF;
st[t] = 1; //将t点纳入到集合s中
res += dis[t];
//尝试用t点更新其它点到集合s的最短距离
for(int j = 1; j <= n; j ++) dis[j] = min(dis[j], g[t][j]);
}
return res;
}
int main()
{
memset(g, 0x3f, sizeof g);
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i++){
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);
}