最小生成树

来源:互联网 发布:sql设置性别默认值 编辑:程序博客网 时间:2024/05/20 20:21

对于连通图G,如果其全部顶点和一部分边构成一个子图,若这一部分边刚好是图中所有顶点连通,且又不形成回路,则称子图G1是原图G的一颗生成树。

对于同一个图,可以有多个不同的生成树,例如下图,是一个带权的无向图,当然它的生成树不知这两种:


由上图可以看出生成树是将原图的全部顶点以最小的边连通的子图,对于有n个顶点的连通图,生成树有n-1条边,若边数小于此数就不可能将各顶点连通,如果边的数量多于n-1,条边,必定会产生回路。

对于一个带权连通图,生成树不同,树中各边上权值总和也不同,权值最小的生成树成为图的最小生成树。

求图的最小生成树在很多领域都有实用价值,例如再用一个图表示城市之间的交通系统,每一个顶点代表一个城市,边的权值表示两城市间的距离,当有多个城市时,可能会有n*(n+1)/2条线路,怎样怎样选择n-1条线路,使各个城市之间总距离最短?可以用最小生成树解决。

下面介绍Prim算法构造最小生成树:

假设图中所有顶点构成一个名为V的集合,最小生成树最终也将具有该集合中所有顶点,另外再设计一个集U,保存最小生成树中的顶点,初始时该集合为空,首先从V集合中取出一个顶点,设为V0,将其加入到U集合中,从V0的邻接点中选择点Vn,使(V0,Vn)边的权值最小,得到最小生成树中一条边,将Vn点加入到U集合中。

接着从V~U集合(即未在U集合中的其他顶点)再选出一个与V0,Vn邻接的顶点(未在U集合中的顶点),找出权值最小边,得到最小生成树的另一边。

按照这个步骤不断重复,最后即可得到最小生成树。

实现Prim算法,需要使用两个辅助数组,tmapvertex和weight,这两个数组长度和顶点数量相同,数组作用如下:

  • 数组tmpvertex在对应位置保存邻接顶点信息,例如,若tmpvertex[1]中顶点V1表示顶点V1与序号为2的顶点(数组序号从0开始,所以tmpvetex[1]中的1加上1,就是2)之间有一条边,每个元素表示一条邻接边,若无邻接边,则设置对应元素的值为-1.
  • 数组weight保存tempvertex对应邻接边的权值
下面演示上图中A图所示无向图创建最小生成树的过程:(PS:图中没有这条边则用∞表示)

 01234tmpvertex0V1V1-1V1weight∞25∞3U 集合V1 V~U集合V2,V3,V4,V5 


首先从顶点V1开始操作,首先将V1加入U集合,再将V1相邻顶点保存到tmpvertex数组,将V1与相邻点构成边的权值保存到weight数组中。从上表中找到第二列权值最小,即第二个顶点与V1相连的边为最短路径,将V2加入U集合,同时从V-U集合中排除V2(将对应数组置0,即weight[1]和tmpvertex[1]都设置为0),完成第一条关键路径查找。

然后从V2顶点出发,将与V2相连的边的权值复制到weight数组中,复制过程要进行比较,如果weight中原来权值比复制权值小,则不复制,复制权值相同时,将与该边连接的顶点也保存到tmpvertex数组中,在本例中,V2相连的只有V4,则其权值复制到weight[3]中,并将V2的顶点信息保存到tmpvertx[3]中,具体如下表:


 01234tmpvertex00V1V2V1weight∞543U 集合V1,V2 V~U集合V3,V4,V5 在上表中第5列权值最小,即第五个顶点与tmpvertex[4]中顶点(顶点V1)相邻边最短路径,将V5加到U集合中,同时V-U集合排除V5,完成第二条关键路径查找。

接下来从V5出发,将与V5相连的边的权值复制到weight数组中,

 01234tmpvertex00V1V50weight∞∞52U 集合V1,V2,V5 V~U集合V3,V4 
在上表中得到第四列权值最小,第四个顶点与tmpvertex[3]中顶点(顶点V5)相连的边为最短路径,将V4加入U集合中,从V-U集合排除V4,完成第三条路径查找。

然后从V4顶点出发,将V4相邻边的权值复制到weight数组中(与V4相连边的顶点都已经访问过,没有数据复制,仍使用weight和tmpvertex数组中原来的值):


 01234tmpvertex00V100weight∞∞5∞U 集合V1,V2,V5,V4 V~U集合V3, 第三个顶点最小即tmpvertex[2]中顶点(V1)相连的边为最短路径,将V3加入到U集合中,同时从V-U集合中排除V3,完成第四条关键路径查找。

这样,U集合中已经包含所有顶点,完成最小树的生成 :

 01234tmpvertex00000weight∞∞∞∞U 集合V1,V2,V5,V4,V3 V~U集合  
编写邻接矩阵图的最小生成树程序:"Prim.c",下面分析程序设计步骤,:(邻接矩阵相关内容参见:邻接矩阵)

#define USED 0    //已使用,加入U集合 #define NOADJ -1  //非邻接顶点 void Prim(MatrixGraph G)//最小生成树{    int i,j,k,min,sum=0;    int weight[VERTEX_MAX];//权值    char tmpvertex[VERTEX_MAX];//临时顶点信息        for(i=1;i<G.VertexNum;i++) //保存邻接矩阵中的一行数据     {        weight[i]=G.Edges[0][i]; //权值         if(weight[i]==MAXVALUE)     //权值为最大值,表示没有这条边            tmpvertex[i]=NOADJ; //非邻接顶点         else            tmpvertex[i]=G.Vertex[0]; //邻接顶点     }    tmpvertex[0]=USED; //将0号顶点并入U集     weight[0]=MAXVALUE; //设已使用顶点权值为最大值     for(i=1;i<G.VertexNum;i++)    {        min=weight[0]; //最小权值         k=i;        for(j=1;j<G.VertexNum;j++) //查找权值最小的一个邻接边             if(weight[j]<min && tmpvertex[j]>0) //找到具有更小权值的未使用边             {                min=weight[j]; //保存权值                 k=j; //保存邻接点序号             }        sum+=min;//累加权值         printf("(%c,%c),",tmpvertex[k],G.Vertex[k]); //输出生成树一条边         tmpvertex[k]=USED; //将编号为k的顶点并入U集         weight[k]=MAXVALUE; //已使用顶点的权值为最大值         for(j=0;j<G.VertexNum;j++) //重新选择最小边             if(G.Edges[k][j]<weight[j] && tmpvertex[j]!=0)            {                weight[j]=G.Edges[k][j]; //权值                 tmpvertex[j]=G.Vertex[k]; //上一个顶点信息             }    }    printf("\n最小生成树的总权值为:%d\n",sum);}

首先定义辅助数组,用来保存临时顶点信息和邻接表的权值;9-16行将V0相邻的点保存到tmpvertex数组中,并将权值保存到weight数组中,若权值为无穷大,tmpvertex对应元素值为NOADJ,非邻接点;  17-18行将序号为0的顶点(V1)加入U集合;21-28行在weight数组中找出权值最小的边,同时对应的tmpvertex数组元素不等于0,(等于0表示对应的顶点添加到U集合中);  然后累加最小生成树的权值,并输出一条边;然后将输出的顶点Vk添加到U集合中;最后将与Vk相邻的点的权值复制到weight数组中。


测试以上程序。(第二行调用邻接矩阵的相关代码参考:点击这里)

#include <stdio.h>#include "linjiejuzhen.c"#include "Prim.c"int main(){    MatrixGraph G; //定义保存邻接表结构的图     int path[VERTEX_MAX];    int i,j,s,t;    char select;    do    {        printf("输入生成图的类型(0:无向图,1:有向图):");        scanf("%d",&G.GraphType); //图的种类        printf("输入图的顶点数量和边数量:");        scanf("%d,%d",&G.VertexNum,&G.EdgeNum); //输入图顶点数和边数         for(i=0;i<G.VertexNum;i++)  //清空矩阵             for(j=0;j<G.VertexNum;j++)                G.Edges[i][j]=MAXVALUE; //设置矩阵中各元素的值为0                 CreateMatrixGraph(&G); //生成邻接表结构的图        printf("邻接矩阵数据如下:\n");        OutMatrix(&G);                printf("最小生成树的边为:\n");        Prim(G);   //调用Prim函数生成最小生成树          printf("继续进行吗?(Y/N)");         scanf(" %c",&select);        getchar();    }while(select!='N' && select!='n');    getch();    return 0;}
对下图所示无向图:



最小生成树如下:












0 0
原创粉丝点击