最小生成树是无向图中的一个问题,很常见。 在无向图中,连通而且不含有圈(环路)的图称为树。最小生成树(MST)的基本模型可以用下面的题目描述:
题目描述 某省调查乡村交通状况,得到的统计表中列出了任意两村庄间的距离。省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可),并要求铺设的公路总长度为最小。请计算最小的公路总长度。
输入 测试输入包含若干测试用例。每个测试用例的第1行给出村庄数目N ( < 100 );随后的N(N-1)/2行对应村庄间的距离,每行给出一对正整数,分别是两个村庄的编号,以及此两村庄间的距离。为简单起见,村庄从1到N编号。 当N为0时,输入结束,该用例不被处理。
输出 对每个测试用例,在1行里输出最小的公路总长度。
样例输入 8 1 2 42 1 3 68 1 4 35 1 5 1 1 6 70 1 7 25 1 8 79 2 3 59 2 4 63 2 5 65 2 6 6 2 7 46 2 8 82 3 4 28 3 5 62 3 6 92 3 7 96 3 8 43 4 5 28 4 6 37 4 7 92 4 8 5 5 6 3 5 7 54 5 8 93 6 7 83 6 8 22 7 8 17 0 样例输出 82
图的两个基本元素是点和边,与此对应,有两种方法可以构造最小生成树T。这两种算法都基于贪心法,因为MST问题满足贪心法的“最优性原理”,即全局最优包含局部最优。prim算法的原理是“最近的邻居一定在MST上”,kruskal算法的原理是“最短的边一定在MST上”。 (1)prim算法:对点进行贪心操作。从任意一个点u开始,把距离它最近的点v加入到T中;下一步,把距离{u,v}最近的点w加入到T中,继续这个过程,直到所有点都在T中。 (2)kruskal算法:对边进行贪心操作。从最短的边开始,把它加入到T中;在剩下的边中找最短的边,加入到T中;继续这个过程,直到所有点都在T中。 在这两个算法中,重要的问题是判断圈。最小生成树显然不应该有圈,否则就不是“最小”了。所以,在新加入一个点或者边的时候要同时判断是否形成了圈。
1、prim算法 设最小生成树的点的集合是U,开始时最小生成树为空,所以U为空。 (1)任取一点,例如点1,放到U中,U={1}。 (2)找离集合U中的点最近的邻居,即1的邻居,是2,放到U中,U={1,2}。 (3)找离U最近的点,是5,U={1,2,5}。 (4)距离U最近的点是5,但是它没有扩展新的点,不符合要求。 (5)加入4,U={1,2,5,4}。 (6)加入3,U={1,2,5,4,3}。所有的点都在U中,结束。 上面的步骤和Dijkstra算法的步骤非常相似,不同的是Dijkstra需要更新U的所有邻居到起点的距离,即“松弛”,而prim则不需要。所以,只需要把Dijkstra的程序简化一些即可。 和Dijkstra算法一样,prim程序也可以用优先队列来查找距离U最近的点,能优化算法。 prim的编程比较麻烦,kruskal更简单且高效。
2、kruskal算法 kruskal算法编程有以下两个关键技术: (1)对边进行排序。可以用STL的sort()函数,排序后,依次把最短的边加入到T中。 (2)判断圈,即处理连通性问题。这个问题用并查集简单而高效,并查集是kruskal算法的绝配。 任意上面的图为例说明kruskal算法的操作步骤
(1)初始时最小生成树为空。令S是以节点i为元素的并查集,在开始时,每个点属于独立的集(为了便于讲解,下表中区分了结点i和集S,把集的编号加上可下划线): (2)加入第一个短边(1-2):T={1-2}。在并查集S中,把结点2合并到结点1,也就是把结点2的集2改成结点1的集1。 (3)加入第二个短边(3-4),T={1-2,3-4}。在并查集S中,结点4合并到结点3. (4)加入第三个最短边(2-5):T={1-2,3-4,2-5}。在并查集S中,把结点5合并到结点2,也就是把结点5的集5改成结点2的集1.在集1中,所有结点都指向了根结点,这样可以避免并查集的长链问题。 (5)第四个最短边(1-5)。检查并查集S,发现5已经属于集1,丢弃这个边。这一步实际上是发现了一个圈。并查集的作用就体现在这里。 (6)加入第五个最短边(2-4)。在并查集S中,把结点4的集并到结点2的集。注意这里结点4的集原本为3,实际上的修改是把结点3的集3改成1。 (7)重复以上操作,直到结束。
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int NUM=105;
int S[NUM];
struct Edge{int u,v,w;}edge[NUM*NUM];
bool cmp(Edge a,Edge b){return a.w<b.w;};
int find(int u){return S[u]==u?u:find(S[u]);}
int n,m;
int kruskal(){
int ans=0;
for(int i=1;i<=n;++i)S[i]=i;
sort(edge+1,edge+m+1,cmp);
for(int i=1;i<=m;++i){
int b=find(edge[i].u);
int c=find(edge[i].v);
if(b==c)continue;
S[c]=b;
ans+=edge[i].w;
}
return ans;
}
int main(){
while(~scanf("%d",&n)){
if(!n)return 0;
m=n*(n-1)/2;
for(int i=1;i<=m;++i)
scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w);
printf("%d\n",kruskal());
}
}
|