美文网首页Noip笔记
[洛谷] P3366 【模板】最小生成树 --- Kruskal

[洛谷] P3366 【模板】最小生成树 --- Kruskal

作者: 续写君 | 来源:发表于2017-08-09 22:13 被阅读0次

题目描述

如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出orz

输入输出格式

输入格式:
第一行包含两个整数N、M,表示该图共有N个结点和M条无向边。(N<=5000,M<=200000)

接下来M行每行包含三个整数Xi、Yi、Zi,表示有一条长度为Zi的无向边连接结点Xi、Yi

输出格式:
输出包含一个数,即最小生成树的各边的长度之和;如果该图不连通则输出orz

输入输出样例

输入样例#1:
4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3
输出样例#1:
7
1.Kruskal算法简介
Kruskal算法一般称作克鲁斯卡尔算法。克鲁斯卡尔算法是一种用来寻找最小生成树的算法。在剩下的所有未选取的边中,找最小边,如果和已选取的边构成回路,则放弃,选取次小边。
2.Kruskal算法思想
先构造一个只含 n 个顶点、而边集为空的子图,把子图中各个顶点看成各棵树上的根结点,之后,从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,即把两棵树合成一棵树,反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。依次类推,直到森林中只有一棵树,也即子图中含有 n-1 条边为止。
  时间复杂度为为O(e^2), 使用并查集优化后复杂度为 O(eloge),与网中的边数有关,适用于求边稀疏的网的最小生成树。
3.Kruskal算法解题过程
首先我们需要解决一个问题,不能构成回路,这时,我们可以先使用并查集的思想,先把每条边存为独立的根,然后,寻找最短边权时,我们可将已经找过的边合并到集内,防止构成回路,选择其他的次小边。

2259.png

接着就是Kruskal处理核心,我们需要先将边进行排序,这样有利于后面选择最小边。
循环0-n次 且 i < n && nEdge != m - 1,为什么要nEdge != m - 1呢?
这个意思就表达了这个图是不连通的,也同样等价于不存在最小生成树。
接着,我们可以查找这两边的根,如果他们的根节点都不一样,就表示能够增加这个边权,它既是最小的,并且也不会构成回路。然后声明一个记住最小边权变量,最后输出既完成本题目。
下面是具体的代码实现

#include <cstdio>
#include <cstdlib>
#define MAXN 200000 + 10//对于100%的数据
using namespace std;

int par[MAXN], Rank[MAXN];//并查集 rank
typedef struct {
    int a, b, price;
}Node;//结构体储存
Node a[MAXN];

int cmp(const void*a, const void *b) {
    return ((Node*)a)->price - ((Node*)b)->price;//用于比较边权
}
void Init(int n) {
    for (int i = 0; i < n; i++) {
        Rank[i] = 0;
        par[i] = i;
    }
}

int find(int x) {
    int root = x;
    while (root != par[root]) root = par[root];
    while (x != root) {
        int t = par[x];
        par[x] = root;
        x = t;
    }
    return root;
}

void unite(int x, int y) {
    x = find(x);
    y = find(y);
    if (Rank[x] < Rank[y]) {
        par[x] = y;
    }
    else {
        par[y] = x;
        if (Rank[x] == Rank[y]) Rank[x]++;
    }
}
//n为边的数量,m为村庄的数量
int Kruskal(int n, int m) {
    int nEdge = 0, res = 0;
    //将边按照权值从小到大排序
    qsort(a, n, sizeof(a[0]), cmp);
    for (int i = 0; i < n && nEdge != m - 1; i++) {
        //判断当前这条边的两个端点是否属于同一棵树
        if (find(a[i].a) != find(a[i].b)) {
            unite(a[i].a, a[i].b);
            res += a[i].price;
            nEdge++;
        }
    }
    //如果加入边的数量小于m - 1,则表明该无向图不连通,等价于不存在最小生成树
    if (nEdge < m - 1) res = -1;
    return res;
}
int main() {
    int n, m, ans;
    scanf("%d%d", &n, &m);
    Init(n);
    for (int i = 0; i < m; i++) {
        scanf("%d%d%d", &a[i].a, &a[i].b, &a[i].price);
        //将村庄编号变为0~m-1(这个仅仅只是个人习惯,并非必要的)
        a[i].a--;
        a[i].b--;
    }
    int rets = Kruskal(m, n);//计算最小生成树
    //输出结果
    if (rets == -1)
        printf("orz");
    else printf("%d", rets);
    return 0;
}

相关文章

网友评论

    本文标题:[洛谷] P3366 【模板】最小生成树 --- Kruskal

    本文链接:https://www.haomeiwen.com/subject/qnuqrxtx.html