带权值的网图通常会遇到这样一个问题:得到一个图中连通所有顶点的最小权值和,或者求得该最小权值和对应的图。这样一类问题在现实生活中也会经常屡见不鲜,比如:现在要在n个村庄之间建立公路,n个村庄最多的情况下可以有n*(n-1)/2 条公路,但是让这n个村庄间接连通,则连接这n个村庄只需要修建n-1条公路,那么如何修建这n条公路会使得所花费的代价最小,这个代价可以是公路的最小长度?这一类问题都可以通过求得图的最小生成树MST(Minimum cost spanning tree)来实现。
最小生成树MST:一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边,或者说权值和最优的连通子图。
求得最小生成树的算法有两个Prim和kruskal这里先介绍prim算法。
prim算法:针对图的顶点作为选取单元,生成两个顶点集合,一个是已经生成的MST集合A,另外一个是没有被选取到的集合B, A+B=U。主要步骤如下所示:
第一步:开始的时候选取B集合中的任意一个顶点作为开始顶点,将该顶点V加入到集合A中,因为MST是针对连通图进行的,所以从哪个顶点开始并不会影响到最终的计算,因此MST的Prim算法也可以判断一个图是否是连通的。
第二步:若集合B不为空则在集合B中查找和集合A顶点权值最优的下一个顶点,然后将该顶点加入到集合A中,然后跟新集合A到各个顶点的最优权值。
第三步:重复第二步操作,知道集合B中的顶点全部加入到了集合A中,此时集合A中的即为权值最优的MST。
需要注意的时,在每次选取新节点到A中的时候需要选取和现有的顶点集相连通的多个顶点中连通权值最小的顶点,比如现在A中有V1,v2两个顶点, B中有V3,V4, v5,而对应的权值为<v1, v3> = 4, <v2, v4> = 7, <v2, v5> = 3,那么久选取距离集合v1,v2权值最优的顶点v5,这里唯一难理解的是集合A的最优权值顶点。这个需要在每次加入新顶点到集合A后进行动态更新。
如下所示:
如上图一样,刚开始选取V1,然后找到集合A(v1)最小的顶点v3加入集合A, 此时集合A变成了A(v1, v3), 然后在找到距离集合A(v1,v3)权值最小的顶点v6加入集合A,集合A就变成了A(v1, v3, v6),重复上述过程,直到集合A拥有全部顶点为止。
代码说明如下:
#define MaxVer 105
#define MaxInf 0x7FFFFFFF
int visited[MaxVer];
int cost[MaxVer];
int map[MaxVer][MaxVer]; // 使用数组的邻接矩阵来存储图
void init_map(int ver_num)
{
for(int i = 0; i <= ver_num; i++)
{
for(int j = 0; j <= ver_num; j++)
{
if (i == j)
{
map[i][j] = 0;
continue;
}
map[i][j] = MaxInf;
}
}
}
int Prim(int ver_num)
{
int sum = 0; // 记录最小生成树权值和
int min_cost, index; // min_cost和index分别记录单次最小权值和顶点
// 初始化visited和cost数组
for (int i = 1; i <= ver_num; i++)
{
visited[i] = 0;
cost[i] = map[1][i];
}
cost[1] = 0;
visited[1] = 1; // 顶点1开始构建MST,将顶点1标记以添加
for(int i = 2; i <= ver_num; i++)
{
index = -1;
min_cost = MaxInf; // 寻找下一个需要加入MST的顶点
for(int j = 2; j <= ver_num; j++)
{
if (!visited[j] && cost[j] < min_cost)
{
index = j; // 遍历剩下的顶点,寻找到与当前MST集合距离权值最小的顶点
min_cost = cost[j]; // 并且记录下对应的权值和顶点index
}
}
if (index == -1 || min_cost == MaxInf)
{
return -1; // 当无法找到下一个最小的权值顶点,有可能此图不连通
}
sum += min_cost; // 将新找到的权值加入到权值和记录
cost[index] = 0; // 将新找到的顶掉标记为已经访问过
visited[index] = 1; // 将新找到的顶掉标记为已经访问过
for (int j = 2; j <= ver_num; j++)
{
if (!visited[j] && map[index][j] < cost[j])
{
cost[j] = map[index][j];
// 遍历更新当前cost数组,当新加入的顶点到j的距离<index, j>
// 比原始最小距离<1, j>(或者是<x, j> x为index之前加入MST的顶点)
// 还要最优时, 跟新这个MST集合到剩余顶点的权值最值,以便下次选取最优顶点
}
}
}
return sum; // 返回最小权值和
}
有上述过程可以看出,prim算法是以图的顶点起始点,每次选取所有顶点上对应的权值最小的边进行构建,所以prim的时间开销和边无关,对于定点数为n时prim的时间复杂度为 O(n^2),所以prim算法更适合求解边数很多的稠密图的MST。