Prim算法
Prim(普利姆)算法是求解最小生成树的经典算法
什么是最小生成树
即给定一个无向图,在这个无向图中选择一些边能够把图中的所有结点都连接起来,并且所有边的长度之和是最短的
下图中红色的边就形成了一棵最小生成树,红色的边将所有的点都连接起来了,所有红色边的权重之和最小。
Prim算法基本思想
我们维护一个集合,最开始集合为空,所有边的权重初始为无穷大(实际上我们并不用设置无穷大,用一个相对较大的数就可以了,代表两点之间不可达)。我们每次都选取距离集合最近的一个点(即距离集合权重最小的点),选取之后就将该点加入集合,并且用该点 更新 与该点相连的 还没加入集合点的权重。
-
初始化
-
选取节点 1 1 1 加入集合,并用该点更新权重(注意:第一步的节点可以随便选),此时集合为 { 1 } \{1\} {1}
- 选取距离集合最近的一个点加入集合,并更新权重,第二次选择节点 2 2 2,集合为 { 1 , 2 } \{1,2\} {1,2}
- 选取距离集合最近的一个点加入集合,并更新权重,第二次选择节点 3 3 3,集合为 { 1 , 2 , 3 } \{1,2,3\} {1,2,3}
- 选取距离集合最近的一个点加入集合,并更新权重,第二次选择节点 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.例题
题目链接
题目描述
给定一个 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 n−1 条边构成的无向连通子图被称为 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 ≤n≤500
- 1 ≤ m ≤ 1 0 5 1 \leq m \leq 10^5 1≤m≤105
- 图中涉及边的边权的绝对值均 不超过 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;
}