图的最小生成树 Kruskal算法
最近小哼迷上了《龙门镖局》,从恰克图到武夷山,从张家口到老河口,从迪化到佛山,从蒙自到奉天,迤逦数千里的商道上,或车马,或舟楫,或驼驮,或肩挑,货物往来,钱财递送,皆离不开镖局押运。商号开在哪里,镖局便设在哪里。古代镖局的运镖,就是运货,也就是现代的物流。镖局每到一个新地方开展业务,都需要对运镖途中的绿林好汉进行打点。好说话的打点费就比较低,不好说话的打点费就比较高。现已知城镇地图如下,顶点是城镇编号,边上的值表示这条道路上打点绿林好汉需要的银子数。
换句话说,镖局的要求就是用最少的边让图连通(任意两点之间可以互相到达),其实就是将多余的边去掉。很显然,要想让有n个顶点的图连通,那么至少需要n-1 条边。如果一个连通无向图不包含回路,那么这就是一棵树,其实这里就是求一个图的最小生成树,下面就是两种生成树方案。
关键的问题是:如何选出这n-1条边,使得边的总长度之和最短呢?
既然要求是让边的总长度之和最短,我们自然可以想到首先选择最短的边,然后选择次短的边……直到选择了n-1条边为止。这就需要先对所有的边按照权值进行从小到大排序,然后从最小的开始选,依次选择每一条边,直到选择了n-1条边让整个图连通为止。将上图中的所有边排序之后如下。
选择过程如下:
到目前为止都很完美,直到选用236这条边时,我们发现此时2号顶点和3号顶点已经连通了,不再需要236这条边。如果加上这条边就会形成回路,那就不是树了,因此需要跳过这条边。
接下来是457这条边,同理这条边我们也不能选用。
最终选用349这条边后,我们已经选用了n-1条边,图已经连通,算法结束,如下。
算法总结:
(1) 将所有边按照权值进行排序
(2) 按照边的权值一一进行加入到图中,当发现两个顶点已经连通,选择的边丢弃,依次进行下去,直到加入了n-1条边
#include <bits/stdc++.h>
using namespace std;
struct edge { //定义边的结构体,方便后面的排序
int u;
int v;
int w;
};
//此处认为边的数量至多9条
struct edge e[10];
//记录点和边的数量
int n, m;
//并查集用到的变量
int f[7], sum, _count = 0;
//f数组大小根据实际情况来确定,要比n的最大值大1
void quicksort(int left, int right) {
int i, j;
struct edge t;
if (left > right) return;
i = left ;
j = right;
while (i != j) {
while (e[j].w >= e[left].w && i < j) j--;
while (e[i].w <= e[left].w && i < j) i++;
//交换
if (i < j) {
t = e[i];
e[i] = e[j];
e[j] = t;
}
}
//最终将基准数归位,将left和i互换
t = e[left];
e[left] = e[i];
e[i] = t;
quicksort(left, i - 1);
quicksort(i + 1, right);
return;
}
//并查集寻找祖先的函数
int getf(int v) {
if (f[v] == v)
return v;
else {
//路径压缩
f[v] = getf(f[v]);
return f[v];
}
}
//并查集合并两个子集合的函数
int merge(int v, int u) {
int t1, t2;
t1 = getf(v);
t2 = getf(u);
if (t1 != t2) {
f[t2] = t1;
return 1;
}
return 0;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= m; i++)
cin >> e[i].u >> e[i].v >> e[i].w;
quicksort(1, m);
cout << endl;
// for (int i = 1; i <= m; i++) {
// cout << e[i].u << " " << e[i].v << " " << e[i].w << endl;
//
// }
//并查集初始化
for (int i = 1; i <= n; i++)
f[i] = i;
//Kruskal算法的核心部分
for (int i = 1; i <= m; i++) {
//判断一条边的两个顶点是否已经连通
if (merge(e[i].u, e[i].v)) {
_count++;
sum = sum + e[i].w;
}
if (_count == n - 1) break;
}
cout << sum;
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