zoukankan      html  css  js  c++  java
  • AcWing 859. Kruskal算法求最小生成树

    题目传送门

    一、Kruskal算法

    1、基本思路:

    (1) 将所有边按权重从小到大排序

    (2) 枚举每条边 (a sim b) ,权重是(c)

        if a,b不在一个集合中 :
            将这条边加入集合中
        结束  
    

    2、与(prim)算法的区别

    • 克鲁斯卡尔算法的基本思想是以边为主导地位,普利姆算法是以点为主导的地位的。
    • (prim)算法适合稠密图,(kruskal)算法适合稀疏图。理由也挺简单的,(kruskal)是按边存的,边少就合适,边多就不适合。稀疏图当然边少,稠密图是点少,但边多,边可能达到节点数的平方,即每个节点都与其它节点有边。

    3、算法模拟

    假如有以下几个城市,之间都有相连的道路:

    根据(kruskal)的原理,我们需要对边权(dis)进行排序,每次找出最小的边。
    排序后,最小的边自然是第(8)条边,于是(4)(6)相连。

    遍历继续,第二小的边是$1$号,$1$和$2$联通。
    再后来是边$3$连接$1$,$4$。
    $dis$也是$14$的还有边$5$,它连接$3$,$4$。

    其次是(dis)(15)的边(4),但是(2)(4)已经相连了,(pass)

    然后是(dis)(16)的两条边(边(2)和边(9)),边(2)连接(1)(3),边(9)连接(3)(6),它们都已经间接相连,(pass)

    再然后就是(dis)(22)的边(10),它连接(5)(6)(5)还没有加入组织,所以使用这边。继续,发现此时已经连接了(n-1)条边,结束,最后图示如下:

    本题与 https://www.acwing.com/problem/content/839/ 是姊妹题,其实(Kruskal)算法就是一个并查集的应用。

    不像(Prim)算法,不用考虑边界,考虑循环(N)次啊,计算最小值啊,还要用堆进行优化啊,这个就是一个并查集,思路简单。

    4、需要回答的问题

    Q:只需要简单结构体即可,不需要邻接表或者邻接矩阵来存,为什么呢?

    A:之所以使用邻接表或邻接矩阵,其实说白了,是按点存的,记录(A)点和(B)点的关系。用结构体存储,其实是按边存的,就是题目说有一条(A-B)的边(权为(C)),我们就存了一个(A-B)权为(C)的边。
    按点存麻烦(邻接表或邻接矩阵),按边存(结构体数组)简单。

    二、完整代码

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 100010;
    const int INF = 0x3f3f3f3f;
    
    int n, m;
    int p[N];
    int res;
    int cnt;
    
    //只需要简单结构体即可,不需要链表
    struct Edge {
        int a, b, w;
        // 需要重载<号,利用w进行排序
        bool operator<(const Edge &W) const {
            return w < W.w;
        }
    } edges[N << 1];
    
    //并查集模板
    int find(int x) {
        if (p[x] != x) p[x] = find(p[x]);
        return p[x];
    }
    
    int kruskal() {
        // 0、初始化并查集
        for (int i = 1; i <= n; i++) p[i] = i;
        //1、按边的权重排序
        sort(edges + 1, edges + 1 + m);
    
        //2、从小到大枚举每一条边[有些边是要放弃滴]
        for (int i = 1; i <= m; i++) {
            int a = edges[i].a, b = edges[i].b, w = edges[i].w;
            //找家族族长
            a = find(a), b = find(b);
            //如果两个节点不在一个集合中
            if (a != b) {
                p[a] = b;   //a认b为祖宗,合并两个集合
                res += w;   //最小生成树中所有树边的权重之和
                cnt++;      //加入了多少条边
            }
        }
        //如果加入的边数小于n-1,说明不连通
        if (cnt < n - 1) return INF;
        //返回所有树边的长度之和
        return res;
    }
    
    
    int main() {
        //读入优化
        ios::sync_with_stdio(false);
        cin >> n >> m;
    
        for (int i = 1; i <= m; i++) {
            int a, b, w;
            cin >> a >> b >> w;
            edges[i] = {a, b, w};
        }
        //调用克鲁斯卡尔算法
        int t = kruskal();
    
        if (t == INF) puts("impossible");
        else printf("%d
    ", t);
    
        return 0;
    }
    
  • 相关阅读:
    初学者一些常用的SQL语句(一)
    java小知识
    ArrayList底层实现原理
    JVM原理
    一个简单的登陆注册页面(希望可以帮到您)
    数据结构
    C++/C
    C的函数指针与指针函数
    函数指针与指针函数
    对数据库通用性的更新操作(ssh)
  • 原文地址:https://www.cnblogs.com/littlehb/p/15336857.html
Copyright © 2011-2022 走看看