2021-01-31 ACM训练笔记

Day10:最小生成树(Minimum Spanning Tree, MST)入门


在这里插入图片描述
常用的两种最小生成树算法:克鲁斯卡尔(Kruskal)算法、普利姆(Prim)算法。

零、什么是最小生成树:

  假定有一个含n个顶点的边带权的无向图,其最小生成树具有如下特征:
  1、是无向图的子图;
  2、包含无向图的全部n个顶点,具有n-1条边(没有回路);
  3、最小生成树包含的所有边的权重和是满足前两条特征的所有子图(这种子图称为生成树)中最小的。

一、Kruskal算法:

  1、基本思想:
    1)、将生成树初始化为含有n个顶点,没有边的非连通图(将所有节点看成一片森林)。
    2)、从原无向图中选择权值最小的边,若该边两端的顶点不在同一个连通块中(两端的顶点不都在生成树中),则将此边作为生成树的一条边;否则舍去此边,寻找其他边中权值最小的边,重复验证直至找到。
    3)、不断地向生成树中添加权值较小的边,直至生成树含有n-1条边。此时,生成树应该满足前面提到的最小生成树的特征,换句话说,此时已经构建出了原无向图的最小生成树。
  2、实现方法:
    从基本思想可以看出,构造最小生成树就是寻找边权值之和最小,包括原无向图所有顶点的一个连通块。因此,可以使用并查集进行处理。
    我的并查集笔记
  3、示例代码:

#include <bits/stdc++.h>

using namespace std;

typedef struct edge_s {
    // 定义表示边的结构体
    int u, v, w;          // 表示顶点u,v之间的边,权值w
    bool friend operator< (const edge_s &x, const edge_s &y) {
        return x.w < y.w; // 重载<运算符, 按权重升序排序
    }
}edge;

const int MAXN = 100 + 10;

int f[MAXN];  // 存储并查集
edge e[MAXN]; // 存储边

int find(int node) {
    // 用并查集查找某顶点对应的代表元
    return node == f[node] ? node : f[node] = find(f[node]);
}

int Kruskal(int n, int m) {
    // 对有n个节点, m条无向边的图构建最小生成树
    // 返回最小生成树的总权值, 构建失败返回-1
    sort(e, e + m);                      // 按权升序排序所有边
    for(int i = 0; i < n; ++i) f[i] = i; // 初始化并查集
    int ans = 0;                         // 生成树的总权值
    int num = 0;                         // 已使用的边数
    for(int i = 0; i < m; ++i) {         // 遍历所有边
        int f1 = find(e[i].u);
        int f2 = find(e[i].v);
        if(f1 != f2) {                   // 判断u,v是否已连接
            f[f2] = f1;
            ans += e[i].w;
            ++num;
        }
        if(num == n - 1) break;          // 已经构建出最小生成树
    }
    if(num != n - 1) return -1; // 遍历所有边后仍无法构建出最小生成树则构建失败
    else return ans;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL); cout.tie(NULL);
    int n, m;
    cin >> n >> m;                       // n个顶点 m条边
    for(int i = 0; i < m; ++i)           // 读入m条边
        cin >> e[i].u >> e[i].v >> e[i].w;
    int weight = Kruskal(n, m);          // 调用示例
    cout << weight << endl;
    return 0;
}

二、Prim算法:

  1、基本思想:
    1)、在原无向图中选择一个顶点,作为初始的生成树。
    2)、在被选择的顶点连接的所有边中选择一条权值最小的边,将该边和该边另一端的顶点加入到生成树中。
    3)、在所有连接树中顶点与树外顶点的边中选择权值最小的边,将选出的边和对应顶点加入生成树中。重复操作直到所有顶点都加入树中。
  2、实现方法:贪心思想
  3、示例代码:

#include<bits/stdc++.h>

using namespace std;

const int MAXN = 100 + 10;
const int inf = 0x3f3f3f3f;

int edge[MAXN][MAXN];
// edge[i][j]存储顶点i到顶点j的边的权值, 边不存在置为inf, 某个顶点到其自身的权值置为0
int used[MAXN];   // used[i]表示顶点i是否已在生成树中
int weight[MAXN]; // weight[i]存储当前生成树与顶点i的最小权值

int Prim(int n, int m) {
    // n个顶点, m条边, 注意顶点编号为1~n
    // 返回最小生成树总权值, 失败返回-1
    memset(used, 0, sizeof(used));
    int index = 1;                                         // 当前加入到生成树的顶点
    used[index] = 1;
    for(int i = 1; i <= n; ++i) weight[i] = edge[index][i];// 更新这个点到其他点的权值
    int ans = 0;                                           // 存放总权值
    for(int i = 1; i < n; ++i) {                           // 遍历剩下的n-1个顶点
        int minw = inf;
        for(int j = 1; j <= n; ++j) {                      // 找出未加入生成树且边权值最小的点
            if(!used[j] && weight[j] < minw) {
                minw = weight[j];
                index = j;
            }
        }
        if(minw == inf) return -1;                         // 如果没有找到, 说明不存在最小生成树
        ans += minw;                                       // 累加权值
        used[index] = 1;                                   // 将这个点加入最小生成树中
        for(int j = 1; j <= n; ++j) {                      // 更新这个点加入后,当前这棵小树到未加入的点的最小权值
            if(!used[j] && weight[j] > edge[index][j])  weight[j] = edge[index][j];
        }
    }
    return ans;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL); cout.tie(NULL); 
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; ++i) {                      // 边权值的初始化, 顶点编号1~n
        for(int j = 1; j <= n; ++j) {
            if(i == j) edge[i][j] = 0;
            else edge[i][j] = inf;
        }
    }
    for(int i = 0, u, v, w; i < m; i++) {              //读入m条边
        cin >> u >> v >> w;
        edge[u][v] = edge[v][u] = min(w, edge[u][v]);  // 消除重边的影响
    }
    cout << Prim(n, m) << endl;
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值