最小生成树(Kruskal & Prim & Boruvka)

例题

kruskal

  • Kruskal算法通过并差集维护,从到小枚举每条边,如果两端点不在一个集合,将两端点所在集合合并,并将边权累加到答案中

  • 时间复杂度为(O(m log m))

  • 评测记录

#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 5005, M = 2e5+5;
struct side {
    int x, y, d;
}e[M];
bool operator < (side a, side b) {
    return a.d < b.d;
}
int n, m, f[N], ans, cnt;
int found(int x) {
    return x == f[x] ? x : (f[x] = found(f[x]));
}
void kruskal() {
    for (int i = 1; i <= m; i++) {
        int x = found(e[i].x), y = found(e[i].y);
        if (x == y) continue;
        f[x] = y;
        ans += e[i].d;
        if (++cnt == n-1) return;//最小生成树上最多n-1条边
    }
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i++)
        scanf("%d%d%d", &e[i].x, &e[i].y, &e[i].d);
    sort(e + 1, e + m + 1);//排序
    for (int i = 1; i <= n; i++) f[i] = i;//并查集初始化
    kruskal();
    if (cnt == n-1) printf("%d
", ans);
    else puts("orn");//奇奇怪怪的输出,其实他没有这个测试点
    return 0;
}

prim

  • Prim算法思想类似于Dijkatra。
    维护一个数组 d[x] 节点 x 与已确定是最小生成树集合中的节点之间权值最小的边的权值
    每次从未标记的节点中选出 d 值最小的点,将其标记,在更新其他点的 d 值

  • 时间复杂度为(O(n^2)),使用堆优化可以到(O(mlogn))(这里写的是堆优化后的板子) 但是这样不如Kruskal方便,
    因此,Prim主要用于稠密图,尤其是完全图的求解

  • 评测记录

#include <queue>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 5005, M = 2e5+5;
struct side{
    int t, d, next;
}e[M<<1];
int head[N], tot;
void add(int x, int y, int z) {
    e[++tot].next = head[x];
    head[x] = tot;
    e[tot].t = y;
    e[tot].d = z;
}
priority_queue< pair<int, int> > q;
int n, m, d[N], ans, cnt;
bool v[N];
void prim() {
    memset(d, 0x3f, sizeof(d));
    q.push(make_pair(d[1] = 0, 1));
    while (!q.empty() && cnt < n) {
        int x, w;
        x = q.top().second;
        w = -q.top().first;
        q.pop();
        if (v[x]) continue;
        v[x] = 1;
        ans += w;
        cnt++;
        for (int i = head[x]; i; i = e[i].next) {
            int y = e[i].t;
            if (d[y] > e[i].d) {
                d[y] = e[i].d;
                q.push(make_pair(-d[y], y));
            }
        }
    }
}
int main() {
    scanf("%d%d", &n, &m);
    while (m--) {
        int x, y, z;
        scanf("%d%d%d", &x, &y, &z);
        add(x, y, z); add(y, x, z);
    }
    prim();
    if (cnt == n) printf("%d
", ans);
    else puts("orn");
    return 0;
}

Boruvka

  • 思路是对于每个联通块找一条连向其他联通块的边权最小的边,然后合并这两个联通块

  • 每次操作都会至少减少一半的联通块,每次需要o(m)得遍历每条边,最多便利logn次,时间复杂度 (O(m log n))

  • 复杂度瓶颈在于如何找联通块之外边权最小的边,在一些有特殊性质的题中可以用数据结构或性质进行优化

#include <cstdio>
#include <cstring>

using namespace std;
const int N = 2e5 + 5;;

int read(int x = 0, int f = 1, char c = getchar()) {
    for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = -1;
    for (; c >='0' && c <='9'; c = getchar()) x = x * 10 + c - '0';
    return x * f;
}

int n, m, p[N], w[N], f[N], cnt, ans;

struct Side {
    int x, y, d;
}a[N];

int Find(int x) {
    return x == f[x] ? x : f[x] = Find(f[x]);
}

int main() {
    n = read(); m = read();
    for (int i = 1; i <= m; ++i)
        a[i] = (Side) {read(), read(), read()};
    for (int i = 1; i <= n; ++i) f[i] = i;
    while (cnt < n - 1) {
        memset(p + 1, 0, n * 4);
        memset(w + 1, 0x3f, n * 4);
        for (int i = 1; i <= m; ++i) {
            int x = Find(a[i].x), y = Find(a[i].y), d = a[i].d;
            if (x == y) continue;
            if (d < w[x]) w[x] = d, p[x] = y;
            if (d < w[y]) w[y] = d, p[y] = x;
        }
        for (int i = 1; i <= n; ++i) {
            if (i != Find(i)) continue;
            int x = Find(p[i]);
            if (i == x) continue;
            f[i] = x, ans += w[i], cnt++;
        }
    }
    printf("%d
", ans);
    return 0;
}
原文地址:https://www.cnblogs.com/shawk/p/13229244.html