Problem Description
当前农村公路建设正如火如荼的展开,某乡镇政府决定实现村村通公路,工程师现有各个村落之间的原始道路统计数据表,表中列出了各村之间可以建设公路的若干条道路的成本,你的任务是根据给出的数据表,求使得每个村都有公路连通所需要的最低成本。
Input
连续多组数据输入,每组数据包括村落数目N(N <= 1000)和可供选择的道路数目M(M <=
3000),随后M行对应M条道路,每行给出3个正整数,分别是该条道路直接连通的两个村庄的编号和修建该道路的预算成本,村庄从1~N编号。
Output
输出使每个村庄都有公路连通所需要的最低成本,如果输入数据不能使所有村庄畅通,则输出-1,表示有些村庄之间没有路连通。
Example Input
5 8
1 2 12
1 3 9
1 4 11
1 5 3
2 3 6
2 4 9
3 4 4
4 5 6
Example Output
19
解题思路
构造最小生成树的算法有普里姆算法(Prim)和克鲁斯卡尔算法(Kruskal)
1.普里姆算法(Prim):归并顶点(加顶点法),与边数无关,适于稠密图;
基本思路:首先取任意一个顶点加入最小生成树,然后再取一个距离已生成的最小生成树的顶点加入最小生成树,直至所有的点都加入最小生成树。d[i]为顶点i与最小生成树的距离,vis[i]为1代表顶点i已加入最小生成树。
2.克鲁斯卡尔算法(Kruskal):归并边(加边法),适于稀疏图。实现过程借助并查集。
基本思路:首先把边从小到大排序,按顺序取出每条边,如果它连接的每两个顶点目前并不连通,则将该边加入最小生成树,否则舍弃该边。
并查集知识补充如下。
并查集详解
代码1:普里姆算法(Prim)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=1010;
int n,m,vis[maxn],d[maxn],mp[maxn][maxn];
long long ans;
bool Prim(){
memset(vis,0,sizeof(vis));
ans=0;
vis[1]=1; //加入顶点1
d[1]=0;
for(int i=2;i<=n;i++) //更新各点与最小生成树的距离
d[i]=mp[1][i];
int tmp;
for(int i=1;i<n;i++){ //还有n-1个顶点待加入
int mn=INF;
for(int j=2;j<=n;j++){
if(!vis[j]&&d[j]<mn){
mn=d[j];
tmp=j;
}
}
if(mn==INF) return false;
ans+=mn;
vis[tmp]=1;
for(int j=2;j<=n;j++){ //更新各点与最小生成树的距离
if(!vis[j]&&mp[tmp][j]<d[j]) d[j]=mp[tmp][j];
}
}
return true;
}
int main(){
while(~scanf("%d%d",&n,&m)){
int u,v,w;
memset(mp,0x3f,sizeof(mp));
for(int i=0;i<m;i++){
scanf("%d%d%d",&u,&v,&w);
mp[u][v]=mp[v][u]=w;
}
if(!Prim()) ans=-1;
printf("%lld\n",ans);
}
return 0;
}
代码2:克鲁斯卡尔算法(Kruskal)
#include<cstdio>
#include<stdlib.h>
#include<algorithm>
using namespace std;
const int maxn=1010;
const int maxm=3010;
int n,m;
int par[maxn];
struct edge{
int from,to;
int w;
bool operator <(const edge &e)const{
return w<e.w;
}
}E[maxm];
int fi(int x){
if(par[x]!=x) return par[x]=fi(par[x]);
return x;
}
//非递归形式
/*
int fi(int t)
{
int r=t;
while(par[r]!=r)
r=par[r];
int i=t,j;
while(i!=r)//压缩路径
{
j=par[i];
par[i]=r;
i=j;
}
return r;
}
*/
bool Same(int x,int y){
return fi(x)==fi(y);
}
void join(int x,int y){
int fx=fi(x),fy=fi(y);
if(fx!=fy) par[fy]=fx;
}
long long kruskal(){
for(int i=1;i<=n;i++)//各顶点自成一个连通分量
par[i]=i;
long long res=0;
sort(E,E+m);
for(int i=0;i<m;i++){
if(Same(E[i].from,E[i].to)) continue;
join(E[i].from,E[i].to);
res+=E[i].w;
}
return res;
}
int main(){
while(scanf("%d%d",&n,&m)==2){
for(int i=0;i<m;i++){
scanf("%d%d%d",&E[i].from,&E[i].to,&E[i].w);
}
long long res=kruskal();
for(int i=1;i<=n;i++){
if(!Same(1,i)){
res=-1;
break;
}
}
printf("%lld\n",res);
}
return 0;
}
利用qsort()排序。
#include<cstdio>
#include<stdlib.h>
using namespace std;
int parent[1001];
typedef struct Node{
int u,v,info;
}Node;
Node cla[3001];
int cost;
int fnd(int t)
{
int r=t;
while(parent[r]!=r)
r=parent[r];
int i=t,j;
while(i!=r)//压缩路径
{
j=parent[i];
parent[i]=r;
i=j;
}
return r;
}
void Kruskal(Node s)
{
int fx=fnd(s.u);
int fy=fnd(s.v);
if(fx!=fy)
{
parent[fx]=fy;
cost+=s.info;
// printf("fx=%d,fy=%d,cost=%d\n",fx,fy,cost);
}
}
int cmp(const void *a,const void *b) //按村庄之间的预算成本从小到大排序
{
return (*(Node *)a).info>(*(Node *)b).info?1:-1;
}
int main()
{
int n,m;
while(~scanf("%d%d",&n,&m))
{
int i,cnt=0;
cost=0;
for(i=1;i<=n;i++)
{
parent[i]=i;
}
for(i=0;i<m;i++)
{
scanf("%d%d%d",&cla[i].u,&cla[i].v,&cla[i].info);
}
qsort(cla,m,sizeof(cla[0]),cmp); //qsort排序
/*1 待排序数组首地址
2 数组中待排序元素数量
3 各元素的占用空间大小
4 指向函数的指针,用于确定排序的顺序*/
for(i=0;i<m;i++)
{
Kruskal(cla[i]);
}
for(i=1;i<=n;i++)
{
if(parent[i]==i)
cnt++;
}
if(cnt!=1)
printf("-1\n");
else
printf("%d\n",cost);
}
return 0;
}