该算法的详细介绍可参考《算法进阶指南》,其中的描述较为容易理解,以下关于该算法的介绍也源自此书。
- Prime 算法 -
最小生成树:
给定一张边带权的无向图
G = ( V, E ), n=| V |, m=| E |。由 V 中全部 n 个顶点和 E 中 n-1 条边构成的无向连通子图
被称为 G 的一棵生成树。边的权值之和最小的生成树
被称为无向图 G 的最小生成树 ( Minimum Spanning Tree, MST ) 。任意一棵最小生成树一定包含无向图中权值最小的边。
Prime 算法:
Prime 算法总是维护最小生成树的一部分
。最初,Prime 算法仅确定1号节点属于最小生成树。在任意时刻,设已确定属于最小生成树的节点集合为 T ,剩余节点集合和 S 。Prime 算法找到 min{z} (z是两个端点分别属于集合 S,T 的权值最小的边),然后把点 x 从集合 S 中删除,加入到集合 T ,并把z累加到答案中。
也就是维护数组 w :若 x 属于 S 则 w[x] 表示节点 x 与集合 T 中的节点之间权值最小的边的权值。若 x 属于 T ,则 w[x] 就等于 x 被加入 T 时选出的最小边的权值,这步操作可类比 Dijkstra 算法
。最后,最小生成树的权值总和就是除了第一个加入集合 T 的点的所有n-1个节点的 w[x] 值之和。
复杂度:
时间复杂度:O(n^2)
可以用二叉堆优化到 O(m log n) 。但用二叉堆优化不如直接使用 Kruskal 算法来得方便。因此,Prim 算法主要用于稠密图
,尤其是完全图的最小生成树
的求解。
算法过程:
1、构建一个 n 阶的邻接矩阵mp(n 和 m 由题目给定)。( mp 、vis、dis、w 数组都是全局定义)
memset(mp, INF, sizeof(mp));
for(int i=1; i<=n; i++) mp[i][i]=0;
for(int i=1; i<=m; i++) {
scanf("%d %d %d", &x, &y, &d);
mp[x][y]=d;
}
2、求最小生成树。
void prim() {
memset(vis, 0, sizeof(vis));//vis=0表示未访问
vis[1]=1;
for(int i=1; i<=n; i++) {//初始化
dis[i]=1;//初始化点i取得最短路径时指向1,即边1-i
w[i]=mp[1][i];//初始化所有点的最短路径为1到顶点的距离,即权值
}
int ans=0, mind, x;
for(int i=1; i<n; i++) {//索引次数为n-1(每次找出一条最短路径)
mind=INF, x=i;
for(int j=1; j<=n; j++) {
if(!vis[j] && w[j]<mind) {
mind=w[j];
x=j;
}
}
vis[x]=1;
ans+=mind;
for(int j=1; j<=n; j++) {
if(!vis[j] && mp[x][j]<w[j]) {
dis[j]=x;
w[j]=mp[x][j];//更新权值
}
}
}
printf("%d\n", ans);//求最小生成树
return ;
}
例题:
Constructing Roads
题意:
给定一个整数 n ,表示村庄数(从1~n编号),接下来 n 行,每行 n 个整数,第 i 行第 j 列的数表示村庄 i 和 村庄 j 之间的距离,然后给定一个整数 q ,接下来 q 行每行有两个数a,b,表示村庄 a、b 之间的路已连通,最后求使得所有村庄互相连通需要修的最短的路。
数据范围:
N (3 <= N <= 100),村庄之间的距离在 [ 1, 1000 ] 范围内,Q (0 <= Q <= N * (N + 1) / 2), (1 <= a < b <= N)
解题思路:
Prime 算法 or Kruskal 算法
以下选择用Prime 算法实现
用 Kruskal 算法实现的代码链接–>- Kruskal 算法 -
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <stack>
#include <queue>
#include <vector>
#include <map>
using namespace std;
#define INF 0x3f3f3f
typedef long long ll;
const int N=105;
int mp[N][N], vis[N], dis[N], w[N];
void init() {
memset(mp, INF, sizeof(mp));
memset(vis, 0, sizeof(vis));//vis=0表示未访问
return ;
}
void prim(int n) {
memset(mp, INF, sizeof(mp));
memset(vis, 0, sizeof(vis));//vis=0表示未访问
vis[1]=1;
for(int i=1; i<=n; i++)
w[i]=mp[1][i];//初始化所有点的最短路径(即权值)为1到顶点的距离
int ans=0, mind, x;
for(int i=1; i<n; i++) {//索引次数为n-1(每次找出一条最短路径)
mind=INF, x=i;
for(int j=1; j<=n; j++) {
if(!vis[j] && w[j]<mind) {
mind=w[j];
x=j;
}
}
vis[x]=1;
ans+=mind;
for(int j=1; j<=n; j++) {
if(!vis[j] && mp[x][j]<w[j])
w[j]=mp[x][j];//更新最短路径
}
}
printf("%d\n", ans);//求得使得所有村庄互相连通的最短路径和
return ;
}
int main() {
int n, q, d;
while(scanf("%d", &n)!=EOF) {//居然是多组输入!!!
init();
for(int i=1; i<=n; i++) {
for(int j=1; j<=n; j++) {
scanf("%d", &d);
mp[i][j]=d;
}
}
scanf("%d", &q);
int a, b;
for(int i=0; i<q; i++) {
scanf("%d %d", &a, &b);
mp[a][b]=mp[b][a]=0;//在这里只要把已修建了路的村庄间的距离记为0就好了
}
prim(n);
}
return 0;
}