程序要求
若在n个城市之间建设通信网络,如何在最节省经费的前提下建立这个通信网?按普里姆算法编写程序,求最小生成树并输出结果。
在每两个城市之间都可以设置一条线路,n个城市间,最多可设置n(n-1)/2条线路,n个城市间建立通信网,只需n-1条线路。
数据结构(C语言版)清华大学出版社 P173 7.4.3
问题转化为:
如何在可能的n(n-1)/2条线路中选择n-1条,能把所有城市(顶点)均连起来,且总耗费最小。
算法分析
最小生成树的MST的性质:
假设N=(V,{E})是一个连通网,U是顶点集V的一个非空子集。若 (u,v)是一条具有最小权值(代价)的边,其中u ∈ U,v∈V-U,则必存在一棵包含边 (u,v)的最小生成树。
本次实验求最小生成树的方法为普里姆算法(Prim),具体思路为:
设N=(V,{E})是连通网(无向网),初始情况:U={u0},(u0V),u0表示最小生成树是从顶点u0出发的; TE是N上最小生成树中边的集合,TE= {},初始状态为空。
在所有uU,vV-U的边(u,v)E中,找一条代价,即权值最小的边(u0,v0)
将(u0,v0)并入集合TE,同时v0并入U。
重复上述操作直至最小生成树是的顶点包括图中所有顶点为止(U=V),则T=(V,{TE})为N的最小生成树 。
普里姆算法利用的图的存储数据结构为邻接矩阵存储,因此算法在图的邻接矩阵基础之上实现。
从键盘中输入一个无向网的顶点数目、边的数目、顶点信息(题目中为顶点名称)、依附于两顶点的边以及它们的权值,输出打印无向网,然后从键盘输入某个顶点信息(题目中为顶点名称u),用来表示最小生成树的出发点。
利用一个辅助数组closedge,以记录从U到V-U具有最小代价的边。
对每个顶点vi∈V-U,在辅助数组中存在一个相应分量closedge[i],它包括两个域,其中lowcost存储该边上的权:closedge[ i-1].lowcost=Min{cost(u,vi)|u∈U},adjvex域存储该边依附的在U中的顶点。
在MiniSpanTree_PRIM函数中,首先利用LocateVex函数求出名为u的顶点的位置,对于其他每个顶点,其closedge的adjvex域赋值为最小生成树的出发点名称u,lowcost域赋值该顶点到最小生成树的出发点u的边的权值(若存在为权值,不存在为0),此时U={u}。
对于其他G.vexnum-1个顶点,找到和顶点u相连(closedge的lowcost域>0)同时和顶点u相连的边的权值最小的那个顶点,将其顶点位置记作k(即为函数minimum的功能),输出其closedge的adjvex域以及该顶点名称,作为最小生成树的边。
将位置为k的顶点并入U集,由于k的并入,V-U到U最短路径可能会相应改变,因此需要调整V-U中顶点的lowcost,若“并入k之后V-U到U的最短路径”比“并入k之前V-U到U的最短路径”要短,那么就要对adjvex与lowcost赋新值,此为MiniSpanTree_PRIM内层循环的功能。
不断重复MiniSpanTree_PRIM外层循环,不断增加最小生成树中顶点的个数,知道最小生成树中顶点包括图中所有顶点为止。
程序代码
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <malloc.h>
#define MAX_VERTEX_NUM 20 //最大顶点个数
#define ERROR -1
#define OK 1
typedef int VRType;//对带权图,则为权值类型。
typedef int Status;
typedef int InfoType;
typedef char VertexType;
typedef struct ArcCell {
VRType adj; //VRType是顶点关系类型。
//对无权图,用1或0表示相邻否;
//对带权图,则为权值类型。
InfoType *info; //该弧相关信息的指针
}ArcCell,AdjMatrix [MAX_VERTEX_NUM][MAX_VERTEX_NUM];
struct{//辅助数组closedge,以记录从U到V-U具有最小代价的边。
VertexType adjvex;//adjvex域存储该边依附的在U中的顶点。
VRType lowcost;//lowcost存储该边上的权
}closedge[ MAX_VERTEX_NUM ];
typedef struct{
VertexType vexs[MAX_VERTEX_NUM]; //顶点向量
AdjMatrix arcs; //邻接矩阵
int vexnum,arcnum; //图的当前顶点数和弧数
}MGraph;
Status LocateVex(MGraph G,VertexType v)//返回名称为v的顶点的位置
{
int i;
//printf("%c ",v);
for(i=0;i<G.vexnum;i++)
{
if(v==G.vexs[i])
{
return i;
}
}
}
Status PrintGraph(MGraph G)//打印图的邻接矩阵
{
int i,j,k;
for(i=0;i<G.vexnum;i++)
{
for(j=0;j<G.vexnum;j++)
printf("%5d ",G.arcs[i][j].adj);
printf("\n");
}
}
//
int minimum(MGraph &G)
//找到和顶点u相连(closedge的lowcost域>0)同时和顶点u相连的边的
//权值最小的那个顶点,将其顶点位置记作k
{
int i,j,k,min;
for(i=0;i<G.vexnum;i++)
{
if(closedge[i].lowcost>0)
//假设第一个closedge的lowcost域值大于0的顶点权值最小,记住其下标,同时此层循环结束
{
min=i;
break;
}
}
k=min;
for(j=k+1;j<G.vexnum;j++)//从之前找到的假定“权值最小”的顶点之后的顶点开始
{
if(closedge[j].lowcost<closedge[min].lowcost&&closedge[j].lowcost>0)
//若某个顶点满足到k的权值比“权值最小”的顶点到k的权值还要小,
//那么这个顶点是权值最小的顶点
min=j;
}
return min;//返回到k权值最小的顶点的位置
}
void MiniSpanTree_PRIM(MGraph G,VertexType u)
//用普里姆算法从顶点名为u的顶点出发构造G的最小生成树T,输出T的各条边。
//VertexType是char类型的!
{
int i,j,k;
k=LocateVex(G,u);//利用LocateVex函数求出名为u的顶点的位置
closedge[k].lowcost=0; //初始,U={u}
for(j=0;j<G.vexnum;++j)
if(j!=k)
{
closedge[j].adjvex=u;//char,closedge的adjvex域赋值为最小生成树的出发点名称u
closedge[j].lowcost=G.arcs[k][j].adj; //int
//lowcost域赋值该顶点到最小生成树的出发点u的边的权值
//(若存在为权值,不存在为0)
}
for(i=1;i<G.vexnum;++i)//选择其余G.vexnum-1个顶点
{
k=minimum(G);
//找到和顶点u相连(closedge的lowcost域>0)同时和顶点u相连的边的
//权值最小的那个顶点,将其顶点位置记作k
printf("%c----%c\n",closedge[k].adjvex,G.vexs[k]); //输出生成树的边
closedge[k].lowcost=0; //第k顶点并入U集
for(j=0; j<G.vexnum; ++j) //调整V-U中顶点的lowcost
if(G.arcs[k][j].adj<closedge[j].lowcost)
//若“并入k之后V-U到U的最短路径”比“并入k之前V-U到U的最短路径”要短
{
closedge[j].adjvex=G.vexs[k];//对adjvex与lowcost赋新值
closedge[j].lowcost=G.arcs[k][j].adj;
}
}//for
}//MiniSpanTree
Status CreateUDN(MGraph &G)//采用数组(邻接矩阵)表示法,构造无向网G。
{
int i,j,k,IncInfo,w;
char v1,v2,u;
printf("Please input the num of vex and arc,as well as the IncInfo:\n");
//从键盘中输入一个无向网的顶点数目、边的数目、顶点信息(题目中为顶点名称)、
//依附于两顶点的边以及它们的权值,输出打印无向网
scanf("%d %d",&G.vexnum,&G.arcnum);
//IncInfo为0则各弧不含其它信息
printf("Please enter %d vertices:",G.vexnum);
for(i=0;i<G.vexnum;i++)
scanf(" %c",&G.vexs[i]); //构造顶点向量
for(i=0;i<G.vexnum;i++) //初始化邻接矩阵
for(j=0;j<G.vexnum;j++)
G.arcs[i][j].adj=999;
for(i=0;i<G.vexnum;i++)
G.arcs[i][i].adj=0;
PrintGraph(G);
for(k=0;k<G.arcnum;k++)//构造邻接矩阵
{
printf("Please input the both vertexs of an arc and its weight:\n");
scanf(" %c %c %d",&v1,&v2,&w); //输入一条边依附的顶点及权值
i=LocateVex(G,v1);
j=LocateVex(G,v2);//确定v1和v2在G中位置
G.arcs[i][j].adj=w;//弧<v1,v2>的权值
G.arcs[j][i]=G.arcs[i][j];//置<v1,v2>的对称弧<v2,v1>
}
return OK;
}//
int main()
{
char u;
MGraph Graph;
CreateUDN(Graph);
PrintGraph(Graph);
printf("PRIM:please input the beginning vertex:\n");
getchar();
scanf("%c",&u);
//输入某个顶点信息(题目中为顶点名称u),用来表示最小生成树的出发点。
MiniSpanTree_PRIM(Graph,u);
}
运行结果
无向网以及无向网的最小生成树(∞用999表示)