求最小生成树
求最小生成树常用的有prim算法和kruskal算法,prime算法分朴素和堆优化两个版本,时间复杂度分别为O(n^2),O(mlogn),适合稠密图。kruskal算法时间复杂度为O(mlogm),适合稀疏图。
最小生成树是指一个无向边构成的连通块,块内无环,且无重边。
prim算法
prim算法过程和Dijkstra非常相似,Dijkstra中dist数组表示的是节点1到其他节点的距离,prim中dist数组表示的是该点到已生成的最小生成树的最短距离。都是从集合中挑出最短距离,然后用该点更新其他点的距离。
例题:https://www.acwing.com/problem/content/description/860/
#include <iostream>
#include <cstring>
using namespace std;
const int N = 510;
// dist数组存放第i个点到连通块的距离
int g[N][N], dist[N], INF = 0x3f3f3f3f;
bool st[N];
int n, m;
int prim() {
int res = 0;
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
for (int i = 0; i < n; i ++ ) {
int t = -1;
// 选出一个距离连通块最近的点
for (int j = 1; j <= n; j ++ ) {
if (!st[j] && ( t == -1 || dist[t] > dist[j] )) t = j;
}
// 当最短距离为INF,则表示无法生成最小生成树
if (dist[t] == INF) {
return INF;
}
res += dist[t];
st[t] = true;
// 用该点更新其他点到连通块的距离
for (int j = 1; j <= n; j ++ ) {
dist[j] = min(g[j][t], dist[j]);
}
}
return res;
}
int main() {
scanf("%d%d", &n, &m);
memset(g, 0x3f, sizeof g);
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 d = prim();
if (d == INF) printf("impossible");
else printf("%d", d);
return 0;
}
kruskal算法
kruskal算法时间复杂度为O(mlogm),借助了并查集的特性(近乎O(1)的判断集合是否相同、将集合合并)。一条边连接两个节点,那么就将该边上的两点所属集合合并。由于事先进行了排序,合并集合时必然使用的是最小权重。
当点a和点b可以连通时,意味着a所属的连通块可以和b所属的连通块连通,那么边a-b就是有效的边,可以记录进入最小生成树中。由于边已经排序,较小的边将会优先记录,当a-b连通块已经被连通,则表示这是重复边,无需记录。
例题:https://www.acwing.com/problem/content/861/
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010, M = 200010;
struct Edge {
int a, b, w;
bool operator < (const Edge &W) const {
return w < W.w;
}
}edges[M];
bool cmp(struct Edge &a, struct Edge &b) {
return a.w < b.w;
}
int n, m, cnt, p[N];
int find(int x) {
if (p[x] == x) return x;
p[x] = find(p[x]);
return p[x];
}
void kruskal() {
int res = 0;
// 将所有边按权重排序
sort(edges, edges + m);
// sort(edges, edges + m, cmp);
// 初始化并查集
for (int i = 1; i <= n; i ++ ) p[i] = i;
// 未连通的点加入并查集
for (int i = 0; i < m; i ++ ) {
int a = edges[i].a, b = edges[i].b, w = edges[i].w;
int pa = find(a), pb = find(b);
if (pa != pb) {
p[pa] = pb;
cnt ++ ;
res += w;
}
}
// n个节点的连通块有n-1个边,如果添加的边小于n-1,则存在未连通的边
if (cnt < n - 1) printf("impossible");
else printf("%d", res);
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 0; i < m; i ++ ) {
int a, b, w;
scanf("%d%d%d", &a, &b, &w);
edges[i] = {a, b, w};
}
kruskal();
return 0;
}