一、题目要求:
某地区经过对城镇交通状况的调查,得到现有城镇间快速道路的统计数据,并提出“畅通工程”的目标:使整个地区任何两个城镇间都可以实现快速交通(但不一定有直接的快速道路相连,只要互相间接通过快速路可达即可)。现得到城镇道路统计表,表中列出了任意两城镇间修建快速路的费用,以及该道路是否已经修通的状态。现请你编写程序,计算出全地区畅通需要的最低成本。
输入格式:
输入的第一行给出村庄数目N (1≤N≤100);随后的N(N−1)/2行对应村庄间道路的成本及修建状态:每行给出4个正整数,分别是两个村庄的编号(从1编号到N),此两村庄间道路的成本,以及修建状态 — 1表示已建,0表示未建。
输出格式:
输出全省畅通需要的最低成本。
测试用例:
4
1 2 1 1
1 3 4 01 4 1 1
2 3 3 0
2 4 2 13 4 5 0
二、问题分析
最小花费连通问题,我们可以理解为求最小生成树,关于做小生成树我们有两种算法:
第一种是Prim算法,简单来讲是从一个顶点出发,寻找权值最小边,再加入新顶点,继续寻找,有点贪心的思想。具体算法请找找其他大佬的解释。
第二是Kruskal算法,或者说加边法,依次选取权值最小边,直到图连通。注意在使用该算法时,需要一个辅助数组VexSet[],用来存储顶点所属连通分量,最终找到最小生成树的标志就是所有顶点在同一连通分量下。其次,我们通常会按边的权值大小对他进行排序,因此我们需要一个排序算法。
我采取的是Kruskal算法。
重要!对于输入数据,我们可以将修建状态为1的路的权值记为0,方便运算。
三、代码呈现
(1)图和边的定义
#include<iostream>
using namespace std;
#define maxN 100
#define arcN 4951
typedef struct AMGraph
{
char vexs[maxN];//村庄
int vexnum,arcnum;//村庄数 路数
}AMGraph;
struct sEdge
{
char Head;//边的始点
char Tail;//边的终点
int lowcost;//边的权值
};
(2)找到顶点对应下标
int LocateVex(AMGraph G,char vex)//找到对应元素角标
{
int i;
for(i=0;i<G.vexnum;i++)
if(G.vexs[i]==vex)
return i;
if(i==G.vexnum)
return -1;
}
(3)根据输入信息构建图
void createGraph(AMGraph &G,sEdge* Edge)//构建图
{
int i,j;
cout<<"请输入村庄数:";
cin>>G.vexnum;
cout<<endl;
int n =G.vexnum;
G.arcnum=n*(n-1)/2;
//顶点数组初始化
cout<<"请输入村庄名称:" ;
for(i=0;i<G.vexnum;i++)
cin>>G.vexs[i];
cout<<endl;
cout<<"请输入道路的起始村庄、目的村庄、修建成本和修建状态:"<<endl;
cout<<endl;
for(i=0;i<G.arcnum;i++)
{
int cost,status;
char v1,v2;
cin>>v1>>v2>>cost>>status;
if(status==1)//如果已修建该路,则cost=0
Edge[i]={v1,v2,0};
else
Edge[i]={v1,v2,cost};
}
}
(4)排序,用于按Edge[].lowcost从小到大进行排序
void Sort(sEdge* Edge,int n)//冒泡排序
{
int i,j;
for(i=0;i<n;i++)
{
for(j=0;j<n-i-1;j++)
{
if(Edge[j].lowcost>Edge[j+1].lowcost)
{
char v1,v2;
sEdge temp=Edge[j];
Edge[j]=Edge[j+1];
Edge[j+1]=temp;
}
}
}
}
(5)基于Kruskal算法求最小费用
int VexSet[maxN];//辅助数组,存储顶点所属的连通分支
int MiniSpanTree_Kruskal(AMGraph G,sEdge* Edge)
{//找到权值最小边,将该边的始点和终点放在同一个连通分支上
createGraph(G,Edge);
cout<<endl;
Sort(Edge,G.arcnum);//将数组中的元素按权值大小排序
int i,j;
//辅助数组初始化
for(i=0;i<G.vexnum;i++)
VexSet[i] =i;
int sum=0;//记录总费用
cout<<"连通顺序为:"<<endl;
//因为排过序了,所以一个边一个边选就行
for(i=0;i<G.vexnum;i++) //遍历边数组
{
int v1=LocateVex(G,Edge[i].Head);//始点的角标
int v2=LocateVex(G,Edge[i].Tail);//终点的角标
int vs1=VexSet[v1];//获取始点的连通分量
int vs2=VexSet[v2];//获取终点的连通分量
//将和终点在同一连通分量的顶点的连通分量全部更新为始点的连通分量
//注意:比如 v1-1 v2/v3/v4-2,则 v2/v3/v4的 VexSet全部更新
//因此要通过循环寻找和 vs2 值相同的所有顶点,全部更新
if(vs1!=vs2) //如果连通分量不等,则要更新连通分量的值
{
cout<<Edge[i].Head<<" "<<Edge[i].Tail<<" "<<endl;//输出最小边的顶点
for(j=0;j<G.vexnum;j++)
if(VexSet[j]==vs2)
VexSet[j] = vs1; //同一连通分支的顶点的VexSet值全部更新
}
sum += Edge[i].lowcost;
}
cout<<endl;
return sum;
}
(6)主函数
int main()
{
AMGraph G;
sEdge Edge[arcN];
cout<<"全省畅通需要的最低成本:"<<MiniSpanTree_Kruskal(G,Edge);
return 0;
}
四、运行结果