图的应用——求最小生成树的Prim算法

程序要求

若在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},(u0V),u0表示最小生成树是从顶点u0出发的; TE是N上最小生成树中边的集合,TE= {},初始状态为空。
在所有uU,vV-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 
#include 
#include 
#include 
#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;//弧的权值
        G.arcs[j][i]=G.arcs[i][j];//置的对称弧
   }


   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);
}

运行结果

图的应用——求最小生成树的Prim算法_第1张图片
无向网以及无向网的最小生成树(∞用999表示)
图的应用——求最小生成树的Prim算法_第2张图片图的应用——求最小生成树的Prim算法_第3张图片

你可能感兴趣的:(数据结构)