最小生成树算法及模板

最小生成树算法及模板

1. 最小生成树算法的类型

img

    1.  如果图是稠密图,则用朴素版Prim算法。
    2.  如果图是稀疏图,则用堆优化版Prim算法或克鲁斯卡尔算法。
    在本博客中,主要讲解朴素Prim算法和克鲁斯卡尔算法。对于堆优化的Prim算法可以用克鲁斯卡尔算法来进行替代。最小生成树问题一般都是无向图,在这里我们不讨论有向图最小生成树问题。最小生成树问题对边的权值正负不敏感。

2. 朴素Prim算法思想

img

    朴素Prim算法的执行过程跟朴素Dijkstra算法类似,这里可以将之前的博客拿过来做一个参考。
    朴素Prim算法的执行过程如下:
        1.  将所有点距离集合的距离初始化成无穷大。
        2.  将所有点进行循环
                找到s集合外离集合距离最近的点t。(这里的距离主要指其它点是否有一条边可以指向这个集合中的任意点,这条边可能会有多个,从多个边中找到最小的权值就是这个点离集合的距离。)
                用t更新其它点到集合的距离。
                st[t] = true;
            其中,s代表已经纳入最小生成树的点,s集合内包含的是最小生成树的节点。
    朴素Prim算法举例如下:

img
img
img
img
img

    当2这个点添加到集合s后,其余节点到集合的距离没有发生变化。

img
img
img

3. 朴素Prim算法模板

int n;      // n表示点数
int g[N][N];        // 邻接矩阵,存储所有边
int dist[N];        // 存储其他点到当前最小生成树的距离
bool st[N];     // 存储每个点是否已经在生成树中


// 如果图不连通,则返回INF(值是0x3f3f3f3f), 否则返回最小生成树的树边权重之和
int prim()
{
    memset(dist, 0x3f, sizeof dist);

    int res = 0;
    for (int i = 0; i < n; i ++ )
    {
        int t = -1;
        for (int j = 1; j <= n; j ++ )
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;

        if (i && dist[t] == INF) return INF;

        if (i) res += dist[t];
        st[t] = true;

        for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], g[t][j]);
    }

    return res;
}

4. 朴素Prim算法例题

https://www.acwing.com/problem/content/860/
#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

int n,m;

const int N = 510;
const int INF = 0x3f3f3f3f;
int g[N][N],dist[N];

//代表当前节点是否在最小生成树中
bool st[N];

int prim(){
    int res = 0;
    memset(dist,0x3f,sizeof(dist));
    //遍历次数,代表当前需要将哪个节点添加到最小生成树中
    for(int i=0;i<n;i++){
        //从不在最小生成树s的节点中找到一个离s距离最短的点
        int t = -1;
        for(int j=1;j<=n;j++){
            if(!st[j] && (t == -1 || dist[t] > dist[j])){
                t = j;
            }
        }
        //代表不存在最小生成树,顶点不连通
        if(i && dist[t] == INF){
            return INF;
        }
        //代表将当前点添加到最小生成树中
        st[t] = true;
        //除了第一次遍历外,将边的权值进行累加(先累加)
        if(i){
            res += dist[t];
        }
        //累加完权值后,通过新添加到集合s的节点t,对其余节点进行距离的更新(再更新)
        for(int j=1;j<=n;j++){
            dist[j] = min(dist[j],g[t][j]);
        }
    }
    return res;
}



int main(){
    memset(g,0x3f,sizeof(g));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        g[u][v] = g[v][u] = min(g[u][v],w);
    }
    int res =  prim();
    if(res == INF){
        printf("impossible");
    }else{
        printf("%d",res);
    }
    return 0;
}

5. 克鲁斯卡尔算法思想

img
img
img

    克鲁斯卡尔算法的执行过程如下:
        1.  将每一条边按照权值进行从小到大排序.
        2.  遍历每一条边(a-b)
        3.  在遍历的过程中,有两种情况:
            3.1 if a节点和b节点不在同一个集合中,那么将其中一个节点加到另外一个节点的集合中。
                累计权值以及将边数+13.2 if a节点和b节点在同一个集合中,那么不处理。
        循环结束后,如果边数小于n-1,那么代表节点间不连通。如果边数等于n-1,那么就代表找到了一颗最小生成树,将累计的权值输出即可。
        需要注意的是,我们可以用并查集来判断两个节点是否在同一个集合中,以及执行两个节点之间的合并操作。
        有关算法的图示,可以见上图。

6. 克鲁斯卡尔算法模板

int n, m;       // n是点数,m是边数
int p[N];       // 并查集的父节点数组

struct Edge     // 存储边
{
    int a, b, w;

    bool operator< (const Edge &W)const
    {
        return w < W.w;
    }
}edges[M];

int find(int x)     // 并查集核心操作
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int kruskal()
{
    sort(edges, edges + m);

    for (int i = 1; i <= n; i ++ ) p[i] = i;    // 初始化并查集

    int res = 0, cnt = 0;
    for (int i = 0; i < m; i ++ )
    {
        int a = edges[i].a, b = edges[i].b, w = edges[i].w;

        a = find(a), b = find(b);
        if (a != b)     // 如果两个连通块不连通,则将这两个连通块合并
        {
            p[a] = b;
            res += w;
            cnt ++ ;
        }
    }

    if (cnt < n - 1) return INF;
    return res;
}

7. 克鲁斯卡尔算法例题

https://www.acwing.com/problem/content/861/
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 100010;
const int M = 200010;

int n,m;

int p[N];

struct Edge{
    int a;
    int b;
    int w;
}edges[M];

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

void quick_sort(Edge q[],int l,int r){
    if(l >= r){
        return;
    }
    int i = l-1;
    int j = r+1;
    Edge x = q[(l+r)>>1];
    while(i < j){
        do{i++;}while(q[i].w < x.w);
        do{j--;}while(q[j].w > x.w);
        if(i < j){
            Edge temp = q[i];
            q[i] = q[j];
            q[j] = temp;
        }
    }
    quick_sort(q,l,j);
    quick_sort(q,j+1,r);
}

void kruskal(){
    //最小生成树的权值之和
    int res = 0;
    //边数
    int cnt = 0;
    //1.按照权值排序
    quick_sort(edges,0,m-1);
    //2.遍历每一条边
    for(int i=0;i<m;i++){
        int a = edges[i].a;
        int b = edges[i].b;
        int w = edges[i].w;
        //如果a,b不在一个集合中
        if(find(a) != find(b)){
            //合并集合
            p[find(a)] = find(b);
            //累加权值
            res += w;
            //边数+1
            cnt++;
        }
    }
    //代表节点不连通
    if(cnt < n-1){
        printf("impossible");
    }else{
        printf("%d",res);
    }
}


int main(){
    scanf("%d%d",&n,&m);
    for(int i=0;i<m;i++){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        edges[i] = {u,v,w};
    }
    //初始化并查集
    for(int i=1;i<=n;i++){
        p[i] = i;
    }
    kruskal();
    return 0;
}
    作者:gao79138
    链接:https://www.acwing.com/
    来源:本博客中的截图、代码模板及题目地址均来自于Acwing。其余内容均为作者原创。
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
posted @   夏目^_^  阅读(61)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示