最小生成树-Prim算法

来源:互联网 发布:178软件源地址 编辑:程序博客网 时间:2024/06/15 09:14

生成树

生成树是连通图的最小连通子图。所谓最小是指:若在树中任意增加一条边,则将出现一个回路;若去掉一条边,将会使之变成非连通图。按照该定义,n个顶点的连通网络的生成树有n个顶点,n-1条边

可知用不同的遍历图的方法,可以得到不同的生成树;从不同的顶点出发,也可能得到不同的生成树。



最小生成树

生成树各边的权值总和称为生成树的权,权最小的生成树称为最小生成树

最小生成树的概念可以应用到许多实际问题中。例如:以尽可能低的总造价建设城市间的通讯网络,以把10个城市联系在一起。在这10个城市中,任意两个城市间都可以建造通讯线路,通讯线路的造价依据城市间的距离不同而有不同的造价,可以构造一个通讯线路造价网络,在网络中,每个顶点表示城市,顶点之间的边表示城市之间可构造通讯线路,每条边的权值表示该条通讯线路的造价,要想使总的造价最低,实际上就是寻找该网络的最小生成树。

常见的构造最小生成树的方法有Prim算法和Kruskal算法。

下面介绍Prim算法



Prim算法

Prim算法通常以邻接矩阵作为储存结构。

它的基本思想是以顶点为主导地位,从起始顶点出发,通过选择当前可用的最小权值边把顶点加入到生成树当中来

1.从连通网络N={V,E}中的某一顶点U0出发,选择与它关联的具有最小权值的边(U0,V),将其顶点加入到生成树的顶点集合U中。

2.以后每一步从一个顶点在U中,而另一个顶点不在U中的各条边中选择权值最小的边(U,V),把它的顶点加入到集合U中。如此继续下去,直到网络中的所有顶点都加入到生成树顶点集合U中为止。 


可行性证明:

设prim生成的树为G0

假设存在Gmin使得cost(Gmin)<cost(G0)

则在Gmin中存在(u,v)不属于G0

将(u,v)加入G0中可得一个环,且(u,v)不是该环的最长边

这与prim每次生成最短边矛盾

故假设不成立,得证.



下面看具体数据和模拟过程


现在有一个存储结构为邻接矩阵的G,有9个顶点,它的arc二维数组如上图所示。

于是Prim算法的代码如下,其中INFINITY为权值极大值,不妨设为0xfffffff,MAXVEX为顶点个数最大值,此处大于等于9即可。

void MinSpanTree_Prim(MGraph G){int min,i,j,k;int adjvex[MAXVEX];//保存相关顶点下标以记录路径 int lowcost[MAXVEX];//保存相关顶点间边的权值lowcost[0]=0;//初始化第一个权值为0,即v0加入生成树//lowcost的值为0,在这里就是此下标的顶点已经加入生成树adjvex[0]=0;//初始化到第一个顶点的下标路径为0for(i=1;i<G.numVertexes;++i){//循环除下标为0外的全部顶点 lowcost[i]=G.arc[0][i];//将V0顶点与之有边的权值存入数组adjvex[i]=0;//初始化都为V0的下标路径 }for(i=1;i<G.numVertexes;++i){min=INFINITY;//初始化最小权值为+inf,通常设置为上限数字,如0xfffffffj=i;k=0;while(j<G.numVertexes)//循环全部顶点 {if(lowcost[j]!=0&&lowcost[j]<min){//如果权值不为零,且权值小于min min=lowcost[j];//则让当前权值成为最小值 k=j;//将当前最小值的下标存入k }j++;}printf("(%d,%d)",adjvex[k],k);//打印当前顶点边中权值最小边 lowcost[k]=0;//将当前顶点的权值设置为0,表示此顶点已经完成任务for(j=1;j<G.numVertexes;++j){//循环所有顶点 if(lowcost[j]!=0&&G.arc[k][j]<lowcost[j]){//若k能到的点未完成任务并且此边小于对应的lowcostlowcost[j]=G.arc[k][j];//将较小权值存入lowcostadjvex[j]=k;//将下标为k的顶点存入adjvex }} }}


1.程序开始运行,我们由第4~5行,创建了两个一维数组lowcostadjvex,长度都为顶点个数9。他们的作用后面会体现。

 

2.第6~8行分别给这两个数组的第一个下标位赋值为0,adjvex[0]=0意思就是现在从顶点V0开始(事实上,最小生成树从哪个顶点开始计算都无所谓,我们假定从V0开始),lowcost[0]=0就表示V0已经被纳入到最小生成树中,之后凡是lowcost数组中的值被设置为0就是表示此下标的顶点被纳入最小生成树

 

3.第9~12行表示我们读取邻接矩阵的第一行数据。将数值赋值给lowcost数组,所以此时lowcost数组值为{0,10,inf,inf,inf,11,inf,inf,inf},而adjvex则全部为0。此时,已经完成了整个初始化的工作,准备开始生成。

 

4.第13~35行,整个循环过程就是构造最小生成树的过程。

 

5.第14~16行,将min设置为了一个极大值0x3fffffff,它的目的是为了之后找到一定范围内的最小权值。j是用来做顶点下标循环的变量,k是用来存储最小权值的顶点下标。

 

6. 第17~24行,循环中不断修改min为当前lowcost数组中最小值,并用k保留此最小值的顶点下标。经过循环后,min=10,k=1。第19行if判断的lowcost[j]!=0表示已经是生成树的顶点不参与最小权值的查找

 

7.第25行,因k=1,adjvex[1]=0,所以打印结果为(0,1),表示V0至V1边为最小生成树的第一条边。如下图所示:

 

8.第26行,此时因k=1,我们将lowcost[k]=0就是说顶点v1纳入到最小生成树中。此时lowcost数组值为{0,0,inf,inf,inf,11,inf,inf,inf}。

 

9.第27~33行,j循环由1至8,因k=1,查找邻接矩阵的第v1行的各个权值,与lowcost的对应值比较,若更小则修改lowcost值,并将k值存入adjvex数组中。因第v1行有18、16、12均比0x3fffffff小,所以最终lowcost数组的值为:{0,0,18,inf,inf,11,16,inf,12}。adjvex数组的值为:{0,0,1,0,0,0,1,0,1}。这里第30行if判断的lowcost[j]!=0也说明v0和v1已经是生成树的顶点不参与最小权值的对比了

 

10.再次循环,由第14行到25行,此时min=11,k=5,adjvex[5]=0。因此打印结构为(0,5)。表示v0至v5边为最小生成树的第二条边,如下图所示:

 

11.接下来执行到33行,lowcost数组的值为:{0,0,18,inf,26,0,16,inf,12}。adjvex数组的值为:{0,0,1,0,5,0,1,0,1}。

 

12.之后经过类似的模拟,如下图:

 


总结

       使用邻接矩阵作为存储结构的Prim算法的时间复杂度为O(V2),如果使用二叉堆与邻接表表示的话,Prim算法的时间复杂度可缩减为O(E log V),其中E为连通图的边数,V为顶点数。

      如果使用较复杂的斐波那契堆,则可将运行时间进一步缩短为O(E + V log V),这在连通图足够密集时(边较多,即E满足Ω(V log V))可较显著地提高运行速度。

 



原创粉丝点击