Problem Description
某省调查乡村交通状况,得到的统计表中列出了任意两村庄间的距离。省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可),并要求铺设的公路总长度为最小。请计算最小的公路总长度。
Input
测试输入包含若干测试用例。每个测试用例的第1行给出村庄数目N ( < 100 );随后的N(N-1)/2行对应村庄间的距离,每行给出一对正整数,分别是两个村庄的编号,以及此两村庄间的距离。为简单起见,村庄从1到N编号。当N为0时,输入结束,该用例不被处理。
Output
对每个测试用例,在1行里输出最小的公路总长度。
Sample Input
3
1 2 1
1 3 2
2 3 4
4
1 2 1
1 3 4
1 4 1
2 3 3
2 4 2
3 4 5
0
Sample Output
3
5
最小生成树
- G= (V,E),若G的一个生成子图是一棵树,则称之为G的一棵生成树(记为T)。
- 最小生成树:无向图G的所有生成树中,树枝的权值总和最小的称为G的最小生成树
prim算法
-
算法思想:将各个点一一加入树中
-
具体做法:
-
先选择一个顶点作为树的根节点,把这个根节点当成一棵树
-
选择图中距离这棵树最近但是没有被树收录的一个顶点,把他收录在树中,并且保证不构成回路
-
按照这样的方法,把所有的图的顶点一一收录进树中。
-
如果没有顶点可以收录:
①如果图中的顶点数量等于树的顶点数量–>最小生成树构造完成
②如果图中的顶点数量不等于树的顶点数量–>此图不连通
对于上图,依次将元素1 -> 3 -> 6 -> 4 -> 2 -> 5加入集合可得到其最小生成树为:
再如使用prim算法的思想,依次将a -> b -> c -> i -> f -> g -> h-> d -> e点加入集合中,可构成如下图所示最小生成树:
Kruskal算法
- kruskal算法思想:
- 贪心: 先将所有的点加入集合,然后依次选取最短的边来组成一棵最小的生成树。n个顶点共有n - 1条边。
- 具体做法:
①将n个顶点看成n个集合。
②按权值由小到大的顺序选择边(先按照权值排序),且所选边应满足两个顶点不在同一个顶点集合内(不构成回路),将该边放到生成树边的集合中。同时将该边的两个顶点所在的顶点集合合并。
③重复②,直到所有的顶点都在同一个顶点集合内。
④可以使用并查集判断两个顶点是否在同一个顶点集合内
例,对于上图,使用Kruskal算法(依次加入边1-3,4-6,2-5,3-6,2-3),可以构成如下图所示的最小生成树:
样例分析
- 样例1 输出1 + 2 = 3
- 样例2 输出1 + 1 + 3 = 5
由以上两图不难发现,计算最小的公路总长度,采用Kruskal算法构建最小生成树即可
AC代码(kruskal算法)
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 105;
const int M = 5010;
int parent[N];//disjoint set
struct edge
{
int a;//village a
int b;//village b
int dis;//distance of a and b
}nums[M];
bool cmp(edge u,edge v)
{
return u.dis < v.dis;//distance : from small to big
}
int find(int x)//disjoint set's find
{
if (x != parent[x]) parent[x] = find(parent[x]);
return parent[x];
}
bool Union(int x,int y)//disjoint set's union
{
int a = find(x);
int b = find(y);
if (a != b)
{
parent[a] = b;
return true;//union succesefully
}
return false;//union false
}
int main()
{
int n;
while (scanf("%d",&n) != EOF && n)
{
for (int i = 1;i <= n;i++) parent[i] = i;//init disjoint set
int m = n * (n - 1) / 2;
for (int i = 1;i <= m;i++) scanf("%d%d%d",&nums[i].a,&nums[i].b,&nums[i].dis);//read data
sort(nums + 1,nums + 1 + m,cmp);
//Kruskal algorithm
int distance = 0;
int cnt = 0;//计数器:集合合并成功的次数
for (int i = 1;i <= m;i++)
{
if (find(nums[i].a) != find(nums[i].b))//当前两个村庄不连通(不在同一个集合)
{
Union(nums[i].a,nums[i].b);//连接两个村庄(集合)
distance += nums[i].dis;//距离加上当前选择的公路的权值
cnt ++;
}
}
if (cnt == n - 1)//n个顶点的需要合并n - 1条边
{
//注意不能用i == n - 1来判断,因为i并不一定等于集合合并成功的次数
printf("%d\n",distance);
continue;
}
}
return 0;
}
AC代码2
不需要用ctr == n-1
来判断,可以直接打印distance
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 105;
const int M = 5010;
int parent[N];//disjoint set
struct edge
{
int a;//village a
int b;//village b
int dis;//distance of a and b
}nums[M];
bool cmp(edge u,edge v)
{
return u.dis < v.dis;//distance : from small to big
}
int find(int x)//disjoint set's find
{
if (x != parent[x]) parent[x] = find(parent[x]);
return parent[x];
}
bool Union(int x,int y)//disjoint set's union
{
int a = find(x);
int b = find(y);
if (a != b)
{
parent[a] = b;
return true;//union succesefully
}
return false;//union false
}
int main()
{
int n;
while (scanf("%d",&n) != EOF && n)
{
for (int i = 1;i <= n;i++) parent[i] = i;//init disjoint set
int m = n * (n - 1) / 2;
for (int i = 1;i <= m;i++) scanf("%d%d%d",&nums[i].a,&nums[i].b,&nums[i].dis);//read data
sort(nums + 1,nums + 1 + m,cmp);
//Kruskal algorithm
int distance = 0;
for (int i = 1;i <= m;i++)
{
if (find(nums[i].a) != find(nums[i].b))//当前两个村庄不连通(不在同一个集合)
{
Union(nums[i].a,nums[i].b);//连接两个村庄(集合)
distance += nums[i].dis;//距离加上当前选择的公路的权值
}
}
//就算Union了所有边,distance也只计入了最小生成树的边(边不在同集合)
printf("%d\n",distance);
}
return 0;
}