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
首先什么是最小生成树:在一个带权值的无环的图中,通过最小的"权值和"把所有的点都连接起来的树,就是最小生成树."带权值"的意思就是假设从A点走到B点需要花费10分钟,那么这个10就是A-B的权值.
prim算法的思想其实就是从一个图中任意选取一个点,和这个点一定有边相连而且不止一条,接下来我们就要去比较这些边的权值了,"最小生成树",如果每条边的值最小,那么最后生成的树的值也就是最小的,说到这里,很多人会说,这不就是"贪心"嘛,没错,这就是一种贪心的想法,我们找到一条边的值最小,假设初始点为A,和A相连的一条权值最小的边的另一头连着B,那么我们新开一个集合C[ ],把A和B添加进去,这时候我们要找下一个了,这时候我们要考虑的是和A,B两个点相连的边中权值最小的,这里要注意一个点,刚才添加的不能算了,接下来找的边不能和C[ ]中的点构成环.循环下去,不断更新,就可以得到一个最小生成树.具体代码详解在下面代码中.
我们先看一下代码:
#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
#define MAX 0x3f3f3f3f
int map[107][107],closest[107];
int n,m;
void prim(int s)
{
int i,j,sum=0;
for(i=1; i<=m; i++)
closest[i]=MAX;
for(i=1; i<=m; i++)
closest[i]=map[s][i];
closest[s]=0;
int now;
for(i=1; i<=m; i++)
{
now=MAX;
int min=MAX;
for(j=1; j<=m; j++)
if(closest[j]&&closest[j]<min)
{
min=closest[j];
now=j;
}
if(now==MAX)
{
break;
}
sum+=min;
closest[now]=0;
for(j=1; j<=m; j++)
if(map[now][j]&&map[now][j]<closest[j])
closest[j]=map[now][j];
}
//cout<<"***"<<i<<"***"<<endl;
if(i<m)
cout<<"?"<<endl;
else
cout<<sum<<endl;
}
int main()
{
while(cin>>n)
{
if(n==0)
return 0;
m=n;
for(int i=1; i<=m; i++)
for(int j=1; j<=m; j++)
map[i][j]=MAX;
for(int i=1; i<=n*(n-1)/2; i++)
{
int a,b,s;
cin>>a>>b>>s;
if(s<MAX)
{
map[a][b]=s;
map[b][a]=s;
}
}
prim(1);
}
return 0;
}
下面一段代码首先是对整个map存储空间进行赋初值MAX,MAX的值无穷大.因为是一个无向连通图,你输入一条A-B的权值,其实相当于同时添加一条B-A.所以第三个for循环要这样写,至于那个s<MAX的判定,其实有没有无所谓啦.
for(int i=1; i<=m; i++)
for(int j=1; j<=m; j++)
map[i][j]=MAX;
for(int i=1; i<=n*(n-1)/2; i++)
{
int a,b,s;
cin>>a>>b>>s;
if(s<MAX)
{
map[a][b]=s;
map[b][a]=s;
}
}
因为我存储的时候是从map[1][1]开始存储的,所以prim[1].其实从哪个位置开始都可以.接下来就是最重要的被调函数了:首先定义的sum就是最后的权值总和.
int map[107][107],closest[107];
int n,m;
void prim(int s)
{
int i,j,sum=0;//sum用来存储最终的权值和
for(i=1; i<=m; i++)//这里循环次数是m次,因为有m个点
closest[i]=MAX;//这里对储存最小生成树的数组赋初值
for(i=1; i<=m; i++)
closest[i]=map[s][i];//这里将主函数传过来的s点与其他各个点的线路权值存储到closest数组里
closest[s]=0;//清零是因为自己到自己没有权值,而且是无回路问题;这里相当于把第一个点加入到最小生成树中.
int now;//定义临时变量记录是否找到权值最小的边的另外一头连接的点,存储新的加入最小生出树的点
for(i=1; i<=m; i++)
{
now=MAX;//赋值为MAX,方便后边进行判断
int min=MAX;//赋值后方便比较大小,在数据较大时,不会出错
for(j=1; j<=m; j++)
if(closest[j]&&closest[j]<min)//如果权值为0,说明已经比较过了(这里在下边的代码中会写出),那么不会进行比较,并且权值要小于当前最小值,有时候会被第一次循环搞糊涂.
{
min=closest[j];//更新当前最小值,
now=j;//更新当前最小值点
}
if(now==MAX)
{
break;
}
sum+=min;//把已经找到的最小权值加入到最终的总权值中
closest[now]=0;//把当前最小权值点加入到最小生成树中,将它的权值赋0
for(j=1; j<=m; j++)//这里再做一次更新,因为你已经找到一个新的点,这个新的点又包含了新的边,所以需要再次更新closest数组
if(map[now][j]&&map[now][j]<closest[j])//因为之前已经将closest中相关点的权值赋值为0,所以再次更新的时候不会改变之前已经找到的点
closest[j]=map[now][j];
}
//cout<<"***"<<i<<"***"<<endl;
if(i<m)
cout<<"?"<<endl;
else
cout<<sum<<endl;
}