求最小生成树

求最小生成树常用的有prim算法和kruskal算法,prime算法分朴素和堆优化两个版本,时间复杂度分别为O(n^2),O(mlogn),适合稠密图。kruskal算法时间复杂度为O(mlogm),适合稀疏图。
最小生成树是指一个无向边构成的连通块,块内无环,且无重边。

prim算法

prim算法过程和Dijkstra非常相似,Dijkstra中dist数组表示的是节点1到其他节点的距离,prim中dist数组表示的是该点到已生成的最小生成树的最短距离。都是从集合中挑出最短距离,然后用该点更新其他点的距离。
image

例题: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;
}
posted @ 2022-02-26 00:02  moon_orange  阅读(60)  评论(0编辑  收藏  举报