本文为普里姆算法(Prim’s Algorithm)对于求最小生成树的另一种算法:
克鲁斯卡尔算法(Kruskal’s Algorithm)请见:最小生成树 | Minimum Spanning Tree | 克鲁斯卡尔Kruskal算法 | C/C++实现
问题描述
请编写一个程序,计算给定加权图
G
=
(
V
,
E
)
G=(V,E)
G=(V,E)的最小生成树的各边权值之和。
输入: 第1行输入G的顶点数n。接下来n行输入表示G的
n
∗
n
n*n
n∗n邻接矩阵A。A的元素
a
i
j
a_{ij}
aij代表顶点
i
i
i到顶点
j
j
j的边的权值。另外,边不存在时记为-1。
输出: 输出G的最小生成树的各边权值总和,占1行。
限制:
1 ≤ n ≤ 100
0 ≤
a
i
j
a_{ij}
aij ≤ 2000
(
a
i
j
≠
−
1
时
)
(a_{ij} \neq -1 时)
(aij̸=−1时)
a
i
j
=
a
j
i
a_{ij}=a_{ji}
aij=aji
G为连通图。
输入示例
5
-1 2 3 1 -1
2 -1 -1 4 -1
3 -1 -1 1 1
1 4 1 -1 3
-1 -1 1 3 -1
输出示例
5
讲解
普里姆算法(Prim’s Algorithm)是求图 G = ( V , E ) G=(V,E) G=(V,E)最小生成树(MST)的代表性算法之一,其基本思路如下:
设图
(
V
,
E
)
(V,E)
(V,E)所有顶点的集合为V,MST中顶点的集合为T。
1.从G中选取任意顶点
r
r
r作为MST的根,将其添加至T。
2.循环执行下述处理直至T = V
在连接T内顶点与V - T内顶点的边中选取权值最小的边
(
p
u
,
u
)
(p_u,u)
(pu,u),将其作为MST的边,并将
u
u
u添加至
T
T
T。
实现这一算法的关键,在于选择边时如何保存权值最小的边。使用邻接矩阵实现的Prim算法需要准备以下变量。这里的 n = ∣ v ∣ n=|v| n=∣v∣。
color[n]:color[v]用于记录v的访问状态WHITE、GRAY、BLACK。
M[n][n]:邻接矩阵,M[u][v]中记录u到v的边的权值。
d[n]:d[v]中用于记录连接T内顶点与V - T内顶点的边中,权值最小边的权值。
p[n]:p[v]用于记录MST中顶点v的父节点。
基于上述变量,可实现Prim算法的下述伪代码
prim()
将所有顶点u的color[u]设为WHITE,d[u]初始化为INFTY
d[0] = 0
p[0] = -1
while true
mincost = INFTY
for i从0至n-1
if color[i] != BLACK && d[i] < mincost
mincost = d[i]
u = i
if mincost == INFTY
break
color[u] = BLACK
for v从0至n-1
if color[v] != BlACK且u和v之间存在边
if M[u][v] < d[v]
d[v] = M[u][v]
p[v] = u
color[v] = GRAY
上述各步骤中,选择顶点u的操作就相当于在连接T内顶点与V - T内顶点的边中选取权值最小的边。另外,只要选定了u,边 ( p [ u ] , u ) (p[u],u) (p[u],u)即会成为构成MST的边。
在使用邻接矩阵实现的普里姆算法中,我们需要遍历图的所有顶点来确定d最小的顶点u,且整个算法的遍历次数与顶点数相等,因此算法复杂度为O(|V|^2)。如果使用二叉堆来选定顶点,效率将大大提高。
AC代码如下
#include<iostream>
using namespace std;
static const int MAX = 100;
static const int INFTY = (1<<21);
static const int WHITE = 0;
static const int GRAY = 1;
static const int BLACK = 2;
int n, M[MAX][MAX];
int prim(){
int u, minv;
int d[MAX], p[MAX], color[MAX];
for(int i = 0; i < n; i++){
d[i] = INFTY;
p[i] = -1;
color[i] = WHITE;
}
d[0] = 0;
while(1){
minv = INFTY;
u = -1;
for(int i = 0; i < n; i++){
if(minv > d[i] && color[i] != BLACK){
u = i;
minv = d[i];
}
}
if(u == -1) break;
color[u] = BLACK;
for(int v = 0; v < n; v++){
if(color[v] != BLACK && M[u][v] != INFTY){
if(d[v] > M[u][v]){
d[v] = M[u][v];
p[v] = u;
color[v] = GRAY;
}
}
}
}
int sum = 0;
for(int i = 0; i < n; i++){
if(p[i] != -1) sum += M[i][p[i]];
}
return sum;
}
int main(){
cin>>n;
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
int e; cin>>e;
M[i][j] = (e == -1) ? INFTY : e;
}
}
cout<<prim()<<endl;
return 0;
}