本文已参与「新人创作礼」活动,一起开启掘金创作之路。
问题
要在n个城市之间假设网络,要求只架设n-1条线路,并保证个城市之间网络链接,已知城市间架设网络的统计数据,其中列出了有可能架设的网络和对应的成本求最低价格方案,以图形方式给出架设方案,并输出求解结果。
解题
很明显,最小生成树,客户(作业代做)要求用prim和邻接矩阵,有难度的是树形图终端打印。
- 宏定义和相关数组
#include <stdio.h>
#include <stdlib.h>
#define MAX_INT 65535//定义一个无穷大的值
#define MIN(x, y) ((x)>(y))?(y):(x)//比较大小的宏定义
#define MAX_N 1001 //城市数量上限
int n=7,m=11;
int v[MAX_N][MAX_N]; //邻接矩阵
int sons[MAX_N][MAX_N];//记录子节点编号
int num_sons[MAX_N],num_endsons[MAX_N],deep[MAX_N];//记录每个节点的儿子数目,每个节点末端儿子数
- 数据输入
客户要图形化,有模有样地给弄一个,哈哈,进行简单的数据采集。
int main()
{
//下标1是第一个点
printf("请输入总城市数量:\n");
scanf("%d",&n);
printf("请输入总的边数量:\n");
scanf("%d",&m);
for(int i=1;i<=n+1;i++){
for(int j=1;j<=n+1;j++){
if(i==j)v[i][j]=0;
else v[i][j]=MAX_INT;//初始化邻接矩阵,所有路径都设为无穷大
}
}
for(int i=0;i<m;i++){//两个城市间成本赋值
int a,b,x;
printf("请某两个城市之间的成本:(输入三个数,用空格分隔)\n");
scanf("%d %d %d",&a,&b,&x);
v[a][b]=v[b][a]=x;
}
- prim算法
开始我们的算法,算法原理很简单,网上一堆。
要注意的是,我们以后要打印最小生成树,而prim算法最直接的是获取父节点矩阵,所以要在经典算法中加入树的构建,需要知道每个节点的子节点,需要记录每个节点儿子的个数。
//开始prim算法
printf("路径如下:");
int lowcost[MAX_N];
int adjvex[MAX_N];
adjvex[1]=0;
lowcost[1]=0;
for (int i = 2; i <= n; ++i) {
lowcost[i]=v[1][i];
adjvex[i]=1;
}
for(int i=0;i<MAX_N;i++){
num_sons[i]=0;//初始化儿子数量数组
num_endsons[i]=0;
deep[i]=1;
}
int min,j,k;
for (int i = 2; i<=n; i++) {//从二号点开始
min = MAX_INT;//将最小值先设为最大
j=1;k=1;
//选出权值最小的,存储在k中
while (j<=n){
if(lowcost[j]!=0&&lowcost[j]<min){//lowcost中的0表示该点已经在集合当中
min = lowcost[j];
k=j;
}
j++;
}
printf("%d->%d ",adjvex[k],k);//从一个点到另一个点,k的父亲是adjvex[k]
int f=adjvex[k];
sons[f][num_sons[f]]=k;
num_sons[f]++;
lowcost[k]=0;//等于0的话代表这个顶点已经加入生成树 下次取权值不会在用到它。
for (j = 1; j<=n; j++) {//更新lowcost数组
if(lowcost[j]!=0&&v[k][j]<lowcost[j]){
lowcost[j]=v[k][j];
adjvex[j]=k;//j节点的前述节点为k
}
}
}
- 打印树
这里打印树形结构采用比较简单的做法,形式比较简单,每一行是一层,节点直接用数字,某节点的第一个子节点在其正下方,第二个子节点在第一个右边,以此类推。需要知道的数据有,何时换行,每个数字空几个空格。
采用广度优先搜索,换行用深度标记,记录每次的历史深度,发现深度有变化,输出换行,空格数量要根据该节点子孙叶子节点决定。
for(int i=1;i<=n;i++){
if(num_sons[i]==0){//该节点是叶子节点(儿子)
int f=adjvex[i];//idx指向直系祖宗点
do{
num_endsons[f]++;
f=adjvex[f];
}while(f!=0);//直到追溯带0点结束
}
}
int p=0,q=1,last_deep=0;//队列指针,上次深度信息
int queue[MAX_N];
queue[0]=1;
printf("\r\n最小生成树如下:");
while(p!=q){
int idx=queue[p];//队首出队,idx是当前操作的节点号
p++;//队首指针前移
if(deep[idx]>last_deep){
printf("\r\n");
last_deep=deep[idx];
}
printf("%d",idx);
for(int i=0;i<num_endsons[idx]-1;i++)
printf(" ");
for(int i=0;i<num_sons[idx];i++){
int s=sons[idx][i];
queue[q++]=s;
deep[s]=deep[idx]+1;
}
}
return 0;
}
测试效果
测试用例
7
11
1 2 5
1 3 11
2 4 12
3 4 24
2 6 8
2 5 2
4 7 18
3 7 20
6 7 3
5 6 4
5 7 15