算法板子:求最小生成树——Prim算法、Kruskal算法

最小生成树的概念:在无向连通图中找到一个子图,子图的顶点个数为n(与父图一致),边的条数为n-1,且边的权重之和最小时就是最小生成树。

1. Prim算法——板子类似朴素Dijkstra算法

#include <iostream>
#include <cstring>
using namespace std;

const int N = 500 + 10, INF = 0x3f3f3f3f;

// d[i]代表点i距离圈外最近邻点的距离; 也就是点i和最近邻点的边的权重
// vis[i]代表点i是否已经出圈; vis[3]=0代表点3还没出圈,还在圈内
// 本题的边多,是稠密图,使用邻接矩阵存储无向图
int d[N], vis[N], g[N][N];

// ans存储最小生成树的边的总权重
// cnt存储最小生成树中顶点的个数
int ans, cnt;

int n, m;

void prim(int s)
{
    // 将每个点到圈外最近邻点的距离初始化为无穷大
    memset(d, INF, sizeof d);
    d[s] = 0;
    
    for (int i = 1; i <= n; i ++ )
    {
        // 找到圈内的一个点,它与圈外最近邻点的距离最小
        int u = 0;
        for (int j = 1; j <= n; j ++ )
            if (!vis[j] && d[j] < d[u]) u = j;
        // 将该圈内点出圈
        vis[u] = 1;
        // 生成树顶点个数加1, 生成树总边权加d[u]
        if (d[u] != INF) cnt ++, ans += d[u];
        // 将出圈点的邻点的d进行更新
        for (int j = 1; j <= n; j ++ )
            if (g[u][j] != INF && d[j] > g[u][j]) d[j] = g[u][j];
    }
    
    // 如果最小生成树中顶点个数等于图中顶点个数,则存在最小生成树,输出总权重; 
    if (cnt == n) cout << ans << endl;
    // 如果最小生成树中顶点个数不等于图中顶点个数,则不存在最小生成树
    else cout << "impossible" << endl;
}

int main()
{
    cin >> n >> m;
    
    memset(g, INF, sizeof g);
    for (int i = 0; i < m; i ++ )
    {
        int a, b, w;
        cin >> a >> b >> w;
        // 无向图存两次边; 邻接矩阵存最小权重边
        g[a][b] = g[b][a] = min(g[a][b], w);
    }
    
    prim(1);
    
    return 0;
}

2. Kruskal算法——用到了并查集

#include <iostream>
#include <algorithm>
using namespace std;

const int N = 1e5 + 10, M = 2e5 + 10;

struct Edge
{
    int a, b, w;
    bool operator< (const Edge &W) const
    {
        return w < W.w;
    }
};

// p[i]代表点i的父亲; p[3]=2代表点3的父亲是2
int p[N];
Edge edges[M];

int n, m;

// ans代表最小生成树的总边权
// cnt代表最小生成树的边数, 应为n-1
int ans, cnt;

int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

void kruskal()
{
    // 按边权升序排序图中的所有边
    sort(edges, edges + m);
    
    // 将每个点看成一个集合; 初始化并查集
    for (int i = 1; i <= n; i ++ ) p[i] = i;
    
    // 枚举每条边,如果这条边连接的两个点不在同一个集合内,就把这条边加入生成树,并合并这两点所在集合
    for (int i = 0; i < m; i ++ )
    {
        Edge e = edges[i];
        if (find(e.a) != find(e.b))
        {
            ans += e.w, cnt ++ ;
            p[find(e.a)] = find(e.b);
        }
    }
    
    // 如果最小生成树中边的个数为n-1,代表存在最小生成树; 否则不存在
    if (cnt == n - 1) cout << ans << endl;
    else cout << "impossible" << endl;
    
}

int main()
{
    cin >> n >> m;
    
    // 将所有边的两点和权重存在edges数组中
    for (int i = 0; i < m; i ++ )
    {
        int a, b, w;
        cin >> a >> b >> w;
        edges[i].a = a, edges[i].b = b, edges[i].w = w;
    }
    
    kruskal();
    
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值