【算法模板】图论:最小生成树

最小生成树

给定一张边带权的无向图G=(V,E),n=|V|,m=|E|。由V中全部n个顶点和E中n-1条边构成的无向连通子图被称为G的一棵生成树。边的权值之和最小的生成树被称为无向图G的最小生成树(Minimum Spanning Tree, MST)。

给定一张无向图G=(V,E), n=|V|,m=|E|。从E中选出k<n-1条边构成G的一个生成森林。若再从剩余的m-k条边中选n-1-k条添加到生成森林中,使其成为G的生成树,并且选出的边的权值之和最小,则该生成树一定包含这m-k条边中连接生成森林的两个不连通节点的权值最小的边。

Prim算法

普利姆 (Prim) 算法是一种用于解决最小生成树问题的算法,又被称为加点法,适用于稠密图,其主要思路如下:

  • 选择任意一个顶点作为起始点,将其加入最小生成树中。

  • 从已选择的顶点集合中选取一个顶点,该顶点与未选择的顶点构成的边权重最小,并且该边的另一端顶点未被选择,将该顶点和边加入最小生成树中。

  • 重复上一步骤 ,直到最小生成树包含了图中的所有顶点。

Kruskal算法

克鲁斯卡尔 (Kruskal) 算法是一种用于解决最小生成树问题的算法,又被称为加边法,适用于稀疏图,其主要思路如下:

  • 建立并查集,每个点各自构成一个集合。

  • 把所有边按照权值从小到大排列,依次扫描每条边(x,y,z)。

  • 若x,y属于同一集合(连通),则忽略这条边,继续扫描下一条。

  • 否则,合并x,y所在的集合,并把z累加到答案中。

  • 所有边扫描完后,上一步处理的边就构成最小生成树。

例题及两种解法

点亮须弥【UUST】 - 蓝桥云课 (lanqiao.cn)

prim

#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
const int INF = 0x3f3f3f3f;
struct side { int to, len; };
int prim(const vector<vector<side>>& edges,int start=0) {
    const int n = edges.size();
    vector<int> vis(n);
    priority_queue<pair<int, int>> q;
    q.push({ 0, 0 });
    int ans = 0;
    while (!q.empty()) {
        int nowp = q.top().second; 
        int d = -q.top().first;
        q.pop();
        if (vis[nowp])continue;
        ans += d;
        vis[nowp] = true;
        for (side s : edges[nowp]) {
            if (vis[s.to])continue;
            q.push({ -s.len,s.to });
        }
    }
    for (bool f : vis)if (!f)return -1;
    return ans;
}
int main() {
    int n, m; cin >> n >> m;
    vector<vector<side>> edges(n);
    while (m--) {
        int u, v, w;
        cin >> u >> v >> w;
        -- u, --v;
        edges[u].push_back({ v,w });
        edges[v].push_back({ u,w });
    }
    cout << prim(edges);
    return 0;
}

kruskal

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<vector>
#include<functional>
#include<algorithm>
#include<numeric>
using namespace std;
struct side { int u, v, w; };
int main() {
    int n, m;
    scanf("%d%d", &n, &m);
    vector<side> edges(m);
    for (side& s : edges)scanf("%d%d%d", &s.u, &s.v, &s.w);
    vector<int> fa(n + 1);
    iota(fa.begin(), fa.end(), 0);
    function<int(int)> find = [&](int x) {
        if (x == fa[x])return x;
        return fa[x]=find(fa[x]);//路径压缩
        };
    sort(edges.begin(), edges.end(), [](const side& s1, const side& s2) {
        return s1.w < s2.w; });
    int ans = 0;
    for (side s : edges) {
        int x = find(s.u);
        int y = find(s.v);
        if (x == y)continue;
        fa[x] = y;
        ans += s.w;
    }
    int flag = false;
    for (int i = 1; i <= n; i++) {
        if (i == fa[i] && !flag)flag = true;
        else if (i == fa[i] && flag) {
            cout << -1;
            return 0;
        }
    }
    cout << ans << endl;
    return 0;
}

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值