图的最小生成树 Prim算法
要用n-1条边将n个顶点连接起来,那么每个顶点都必须至少有一条边与它相连(要不然这个点就是一个"孤立"的点了)。那就随便选一个顶点开始(反正最终每个顶点都是要选到的),看看这个顶点有哪些边,在它的边中找一个最短的。例如在下面这个图中,首先选择1号顶点,1号顶点有两条边分别是1→2和1→3,其中1→2比较短。“每个顶点都必须至少有一条边与它相连”,那不妨先选择这条最短的边1→2,通过它就将1号顶点和2号顶点连接在一起。这里1号顶点和2号顶点已经被选中。下图中白色的顶点就是被选中的点。
连接完1号和2号顶点后,那剩下的顶点一定要向这两个顶点靠近,越近越好。于是接下来便开始枚举1号和2号顶点所有的边,看看哪些边可以连接到没有被选中的顶点,并且边越短越好。也就是在1→3、2→3和2→4这三条边中选出最短的一条。这三条边中1→3最短,通过这条边,就可以将3号顶点与1号、2号顶点连接在一起。接下来只需采用刚才的方法继续寻找剩下的顶点中离1、2和3号顶点最近的顶点,也就是继续在1号、2号和3号顶点所有的边中找出一条最短边可以连接到没有被选中的顶点。这次选中的是4号顶点。照此方法,一共重复操作n-1 次,直到将所有顶点都选中,算法结束。这个方法就像是一棵"生成树"在慢慢长大,从1个顶点长到具有n个顶点的树。
总结:
将图中所有的顶点分为两类:树顶点(已被选入生成树的顶点)和非树顶点(还未被选入生成树的顶点)。首先选择任意一个顶点加入生成树(你可以理解成为生成树的根)。接下来要找出一条边添加到生成树,这需要枚举每一个树顶点到每一个非树顶点所有的边,然后找到最短边加入到生成树。照此方法,重复操作n-1次,直到将所有顶点都加入生成树中。
具体实现步骤:
1. 从任意一个顶点开始构造生成树,假设就从1号顶点,首先将顶点1加入生成树中,用一个一维数组book来标记哪些顶点已经加入了生成树。
2. 用数组 dis记录生成树到各个顶点的距离。最初生成树中只有1号顶点,有直连边时,数组dis中存储的就是1号顶点到该顶点的边的权值,没有直连边的时候就是无穷大,即初始化 dis数组。
3. 从数组dis中选出离生成树最近的顶点(假设这个顶点为j)加入到生成树中(即在数组dis中找最小值)。再以j为中间点,更新生成树到每一个非树顶点的距离(就是松弛),即如果 dis【k】>e【j】【k】则更新 dis【k】=e【j】【k】。
4. 重复第3步,直到生成树中有n个顶点为止。
#include <bits/stdc++.h>
using namespace std;
int edg[10][10];
int m, n;
int book[10];
int _count = 0;
int sum = 0;
int main() {
clock_t start, finish;
//读入数据
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (i == j)
edg[i][j] = 0;
else
edg[i][j] = 1e6;
}
//TODO
}
//读入边的信息
for (int i = 1; i <= m; i++) {
int x, y, z;
cin >> x >> y >> z;
edg[x][y] = z;
edg[y][x] = z;
}
start = clock();
//初始化
int dis[10];
for (int i = 1; i <= n; i++) {
dis[i] = edg[1][i];
}
book[1] = 1, _count++;
while (_count < n) {
int min = 1e6;
int index = 0;
//找到距离最近的点
for (int j = 1; j <= n; j++) {
if (dis[j] < min and book[j] == 0) {
min = dis[j];
index = j;
}
}
//选中该点
book[index] = 1; //加入选定点
_count++; //点数加1
sum += min; //求和边长
// cout<<index<<":"<<min<<" ";
//更新dis的数值
for (int j = 1; j <= n; j++) {
if (dis[j] > edg[index][j] and book[j] == 0)
dis[j] = edg[index][j];
}
}
cout << endl << sum;
finish = clock();
cout << endl << "the time cost is:" << double(finish - start) / CLOCKS_PER_SEC << endl;
return 0;
}
测试数据:
6 9
2 4 11
3 5 13
4 6 3
5 6 4
2 3 6
4 5 7
1 2 1
3 4 9
1 3 2
测试结果: 19
从上面介绍的两个最短生成树算法可以看出,如果所有的边权都不相等,那么最小生成树是唯一的。Kruskal算法是一步步地将森林中的树进行合并,而Prim算法则是通过每次增加一条边来建立一棵树。Kruskal算法更适用于稀疏图,Prim算法适用于稠密图。