kruskal算法

127 阅读2分钟

本题和上一题一样是求最小生成树。

题目回忆

给定一个 nn 个点 mm 条边的无向图,图中可能存在重边和自环,边权可能为负数。

求最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible

给定一张边带权的无向图 G=(V,E),其中 V 表示图中点的集合,E 表示图中边的集合,n=|V|,m=|E|。

由 VV 中的全部 nn 个顶点和 EE 中 n1n−1 条边构成的无向连通子图被称为 GG 的一棵生成树,其中边的权值之和最小的生成树被称为无向图 GG 的最小生成树。

题目分析

相较于 prim 算法对点进行搜索, kruskal 则是对边进行操作,这有点类似于 dijkstra 算法和 spfa 算法,不过 kruskal 算法的限制明显更小。

首先我们读入边的两个端点和边权,定义一个三元组(即用一个二元组,二元组的第二个元素仍旧是一个二元组),我们按 {边权,{端点a,端点b}} 的方式依次读进三元组,再使用 sortsort 对其进行排序,主要是按边权进行排序。

然后我们依次按边权从小到大的顺序读取三元组的元素,利用并查集的方式看看是否可以将所有端点连接到一棵树上,由于边权是从小到大排序,则最终若可以得到最小生成树,所选择的 n1n-1 条边的边权和一定是最小。同时我们定义一个标记,在进行并查集操作的过程中,我们每次更改某个端点的父节点时,将标记自加 11,最终判断是否等于 n1n-1 来判断是否能形成最小生成树。

Accept代码

#include <bits/stdc++.h>

using namespace std;

const int N = 200010;
pair<int, pair<int, int>> g[N];
int p[N];
int n, m;
int res;

int find(int x)
{
    return x == p[x] ? p[x] : p[x] = find(p[x]);
}

bool kruskal()
{
    int cnt = 0;
    for (int i = 0; i < m; i ++)
    {
        auto [w, x] = g[i];
        int a = find(x.first), b = find(x.second);
        if (a != b)
        {
            p[b] = a;
            res += w;
            cnt ++;
        }
    }
    if (cnt != n - 1) return false;
    return true;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    cin >> n >> m;
    for (int i = 0; i < m; i ++)
    {
        int a, b, w;
        cin >> a >> b >> w;
        g[i] = {w, {a, b}};
    }
    sort(g, g + m);
    for (int i = 1; i <= n; i ++) p[i] = i;
    if (kruskal()) cout << res;
    else cout << "impossible";
    return 0;
}