Prim算法

Prim算法

Prim(普利姆)算法是求解最小生成树的经典算法

什么是最小生成树

即给定一个无向图,在这个无向图中选择一些边能够把图中的所有结点都连接起来,并且所有边的长度之和是最短的

下图中红色的边就形成了一棵最小生成树,红色的边将所有的点都连接起来了,所有红色边的权重之和最小

在这里插入图片描述

Prim算法基本思想

我们维护一个集合,最开始集合为空,所有边的权重初始为无穷大(实际上我们并不用设置无穷大,用一个相对较大的数就可以了,代表两点之间不可达)。我们每次都选取距离集合最近的一个点(即距离集合权重最小的点),选取之后就将该点加入集合,并且用该点 更新 与该点相连的 还没加入集合点的权重。

  1. 初始化
    在这里插入图片描述

  2. 选取节点 1 1 1 加入集合,并用该点更新权重(注意:第一步的节点可以随便选),此时集合为 { 1 } \{1\} {1}

在这里插入图片描述

  1. 选取距离集合最近的一个点加入集合,并更新权重,第二次选择节点 2 2 2,集合为 { 1 , 2 } \{1,2\} {1,2}

在这里插入图片描述

  1. 选取距离集合最近的一个点加入集合,并更新权重,第二次选择节点 3 3 3,集合为 { 1 , 2 , 3 } \{1,2,3\} {1,2,3}

在这里插入图片描述

  1. 选取距离集合最近的一个点加入集合,并更新权重,第二次选择节点 7 7 7,集合为 { 1 , 2 , 3 , 7 } \{1,2,3,7\} {1,2,3,7}

在这里插入图片描述

就这样一直迭代下去,直到将所有的点都加入到集合中。在迭代的过程中我们可以记录最小生成数的权值和,也可以将最小生成树中的边打印出来。

2.代码实现

输入:

7 11
1 2 18
1 7 18
1 6 19
2 7 20
2 3 8
3 4 20
4 7 15
4 6 16
4 5 9
5 6 3
6 7 15

输出:

1 ---> 2
2 ---> 3
1 ---> 7
7 ---> 4
4 ---> 5
5 ---> 6
dist 18 8 8 9 3 3 15 
距离 -> 71

实现代码:

#include<bits/stdc++.h>
using namespace std;

const int N = 510,INF = 0x3f3f3f3f;
int g[N][N],dist[N];// g是邻接矩阵 用于存储边 g[a][b] = w 代表 a 到 b 的距离为w
                    //dist[i] 代表与i点相连的最短一条边的距离                    

int n,m;    //要求给定n个点 和 m条边
bool st[N]; //st记录某一个点是否加入集合 st[i] = true 代表i点已经加入集合 反之则没有

int prim(){
    //最开始初始化为无穷大
    memset(dist,0x3f,sizeof dist);
    int ans = 0;
    //由于我们需要将 给定的 n个点都加入集合,所以需要循环n次
    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;
        }
        
        //从集合中找出 距离 当前点 t 最近的点
        //pre -> t 的边就是新加入最小生成树的边
        if(i > 0){
            int ans = INF;
            int pre = -1;
            for(int j = 1;j <= n;j++){
                if(st[j]){
                    if(ans > g[j][t]){
                        ans = g[j][t];
                        pre = j;
                    }
                }
            }
            printf("%d ---> %d\n",pre,t);
        }
        
        //如果存在有些点不可达,就无法形成最小生成树,直接返回
        //当 i = 0 时,此时集合是为空的,第一个要加入到集合中的点是1,此时还没加入到集合
        //并且也没更新距离,所以需要跳过
        if(i > 0 && dist[t] == INF) return INF;
        
        //将 点t 加入到集合中
        st[t] = true;
        
        //当i > 0,比如 i = 1,也就是第二轮循环,此时集合中有2个点,才能确定一条边
        if(i > 0) ans += dist[t];//记录最小生成树每条边的距离
        
        //用点t 更新距离
        for(int j = 1;j <= n;j++){
            dist[j] = min(dist[j],g[t][j]);
        }
    }
    return ans;
}

int main(){
    cin>>n>>m;
    memset(g,0x3f,sizeof g);
    while(m--){
        int a,b,w;
        scanf("%d%d%d",&a,&b,&w);
        //因为是无向图 即 a 可以到达 b,b 也可以到达 a
        //可能会存在重边的情况,我们只需要记录最小值
        g[a][b] = g[b][a] = min(g[a][b],w);
    }
    int t = prim();
    printf("dist ");
    for(int i = 1;i <= n;i++) printf("%d ",dist[i]);
    puts("");
    if(t == INF) puts("不存在最小生成树!!!");
    else printf("距离 -> %d\n",t);
    return 0;
}

3.例题

题目链接

Prim算法求最小生成树

题目描述

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

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

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

V V V 中的全部 n n n 个顶点和 E E E n − 1 n−1 n1 条边构成的无向连通子图被称为 G G G 的一棵生成树,其中边的权值之和最小的生成树被称为无向图 G G G最小生成树

输入格式

第一行包含两个整数 n n n m m m

接下来 m m m 行,每行包含三个整数 u , v , w u,v,w u,v,w,表示节点 u u u 和 节点 v v v 之间存在一条权值为 w w w 的边。

输出格式

共一行,若存在最小生成树,则输出一个整数,表示最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible

数据范围
  • ≤ n ≤ 500 \leq n \leq500 n500
  • 1 ≤ m ≤ 1 0 5 1 \leq m \leq 10^5 1m105
  • 图中涉及边的边权的绝对值均 不超过 10000 10000 10000

输入样例:

4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4

输出样例:

6

时间复杂度: O ( n 2 ) O(n^2) O(n2)

C++代码:

#include<bits/stdc++.h>
using namespace std;

const int N = 510,INF = 0x3f3f3f3f;
int g[N][N],dist[N];
int n,m;
bool st[N];

int prim(){
    memset(dist,0x3f,sizeof dist);
    int ans = 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;
        st[t] = true;
        if(i) ans += dist[t];
        for(int j = 1;j <= n;j++) dist[j] = min(dist[j],g[t][j]);
    }
    return ans;
}

int main(){
    cin>>n>>m;
    memset(g,0x3f,sizeof g);
    while(m--){
        int a,b,w;
        scanf("%d%d%d",&a,&b,&w);
        g[a][b] = g[b][a] = min(g[a][b],w);
    }
    int t = prim();
    if(t == INF) puts("impossible");
    else printf("%d\n",t);
    return 0;
}
  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值