假设图有n个点m条边
1、朴素版Prim O(n^2)
通常用于稠密图。
1.1、算法步骤
- 初始化所有点的 d i s [ i ] = ∞ dis[i]=\infty dis[i]=∞,S为空集
-
f
o
r
(
i
=
0
;
i
<
n
;
i
+
+
)
for (i=0; i < n; i ++ )
for(i=0;i<n;i++)
- 找到不在集合S中,dis最小的点x。(S为空集则随便找一个点)
- x加入S中
- 用x更新S外的点到集合S的距离dis
其他点到集合S的距离:假设某个点有多条边连向集合内的点,其中最小的边就是这个点到集合的距离;如果没有边连向集合内的点,则距离为
∞
\infty
∞。
因为这边新加进集合的点只有x,因此只要看与x连接的最小的边即可。
1.3、算法实现
稠密图采用邻接矩阵存储。
res更新要写在dis更新前面,防止出现负的自环使dis变小,或者已经进入S的点的dis再被更新,计入res中。
最好去掉自环。
#include <iostream>
#include <cstring>
using namespace std;
const int N = 505, INF = 0x3f3f3f3f;
int n, m;
int g[N][N], dis[N];
bool flag[N];
int prim() {
memset(dis, 0x3f, sizeof dis);
memset(flag, 0, sizeof flag);
int res = 0;
for (int i = 0; i < n; i ++ ) {
int x = -1;
for (int j = 1; j <= n; j ++ ) {
if (!flag[j] && (x == -1 || dis[x] > dis[j])) x = j;
}
flag[x] = true;
// 不是0号点且最近的点也是无穷,代表图不连通,无最小生成树
if (i && dis[x] == INF) return INF;
// res更新要写在dis更新前面,防止出现负的自环使dis更新,或者已经进入S的点的dis再被更新,计入res中。
if (i) res += dis[x];
for (int j = 1; j <= n; j ++ ) {
dis[j] = min(dis[j], g[x][j]);
}
}
return res;
}
int main() {
scanf("%d%d", &n, &m);
memset(g, 0x3f, sizeof g);
while (m -- ) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
if (u != v) // 最好去掉自环
g[u][v] = g[v][u] = min(g[u][v], w); // 重边保留最小
}
int ans = prim();
if (ans == INF) printf("orz\n");
else printf("%d\n", ans);
return 0;
}
堆优化版Prim O(mlogn)
由于时间复杂度和kruskal差不多,且写起来较复杂,故很少用。
思路和迪杰斯特拉堆优化相同,在 “找到不在集合S中dis最小的点x” 这步采用堆优化。
注意几点细节:
- 去掉自环,防止负自环影响答案
- 在 “用x更新S外的点到集合S的距离dis” 这步需要加:
if (flag[y] == true) continue; // 防止已经进入S的点的dis再被更新
此处和迪杰斯特拉不同,因为最小生成树边不要求正,所以可能重复更新。 - 计算完所有dis,如果存在
dis[i]=INF
则代表这个点永远走不到。
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 10005, M = 500005, INF = 0x3f3f3f3f;
typedef pair<int, int> PII;
int n, m;
int dis[N];
int head[N], lnk[M], val[M], nxt[M], idx;
bool flag[N];
void add (int u, int v, int w) {
lnk[idx] = v;
val[idx] = w;
nxt[idx] = head[u];
head[u] = idx ++ ;
}
int prim() {
memset(dis, 0x3f, sizeof dis);
memset(flag, 0, sizeof flag);
int res = 0;
priority_queue<PII, vector<PII>, greater<PII> > heap;
heap.push({0, 1}); // {dis[1], 1号点}
dis[1] = 0;
while (!heap.empty()) {
int x = heap.top().second;
heap.pop();
if (flag[x]) continue;
flag[x] = true;
for (int i = head[x]; i != -1; i = nxt[i]) {
int y = lnk[i], w = val[i];
if (flag[y] == true) continue; // 防止已经进入S的点的dis再被更新
dis[y] = min(dis[y], w);
heap.push({dis[y], y});
}
}
for (int i = 1; i <= n; i ++) {
// 从1号点出发永远无法走到i,代表图不连通,无最小生成树
if (dis[i] == INF) return INF;
res += dis[i];
}
return res;
}
int main() {
scanf("%d%d", &n, &m);
memset(head, -1, sizeof head);
while (m -- ) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
if (u != v) {// 最好去掉自环
add(u, v, w);
add(v, u, w);
}
}
int ans = prim();
if (ans == INF) printf("orz\n");
else printf("%d\n", ans);
return 0;
}