最小生成树

来源:互联网 发布:java在线视频网站源码 编辑:程序博客网 时间:2024/06/06 09:04

一个有n个结点的连通图的生成树是原图的极小连通子图,使用不同的寻找方法或从不同的初始结点出发都可以得到不同的生成树。对于n个结点的无向连通图,它的生成树有且只有n-1条边。

如果无向连通图是一个带权图,那它的所有生成树中必有一棵边的权值总和最小生成树,那么这棵树就是最小生成树。

构造有n个结点的无向连通带权图的最小生成树,必须满足以下条件:

(1)构造的最小生成树必须包括n个结点;

(2)构造的最小生成树中有且只有n-1条边;

(3)构造的最小生成树中不存在回路。

最小生成树典型的构造方法有Prim算法和Kruskal算法,本文只实现Prim算法。

Prim算法思想:

假设G=(V,E)为一个带权图,设置两个新的集合U和T,其中U用于存放带权图G的最小生成树的结点的集合,T用于存放带权图G的最小生成树的边的集合。

令集合U的初值U={u0}(u0为构造最小生成树的初始结点),集合T的初值T={ }。从所有结点u∈U和结点v∈V-U的带权边中选出具有最小权值的边(u,v),将结点v加入集合U中,将边(u,v)加入集合T中。如此不断重复,当U=V时则是最小生成树构造完毕。

对题例如图,进行Prim函数设计,令弧头结点等于弧尾结点时权值等于0(即邻接矩阵对角元素为0)。


首先定义最小生成树类MinSpanTree:

package Map;/*** @author sun* 创建时间:2017年5月10日下午2:55:40*///最小生成树类public class MinSpanTree {Object vertex;//边的弧头结点数据int weight;//权值MinSpanTree(){}MinSpanTree(Object obj,int w){vertex = obj;weight = w;}}

设计Prim类:

package Map;/*** @author sun* 创建时间:2017年5月10日下午3:01:19*///prim函数设计public class Prim {static final int maxWeight = 9999;public static void prim(AdjMWGraph g,MinSpanTree[] closeVertex)throws Exception{//用prim算法建立带权图g的最小生成树int n = g.getNumOfVertices();//获取图中结点数量int minCost;int[] lowCost = new int[n];int k = 0;//初始结点u0与其他n-1个结点的连接权值for(int i=1;i<n;i++)lowCost[i] = g.getWeight(0, i);//lowCost的初始值MinSpanTree temp = new MinSpanTree();//从结点0出发构造最小生成树temp.vertex = g.getValue(0);closeVertex[0] = temp;//保存结点0lowCost[0] = -1;//标记结点,说明已经使用for(int i=1;i<n;i++){//寻找当前结点0与其他结点最小权值的边所对应的弧头结点kminCost = maxWeight;for(int j=1;j<n;j++){if(lowCost[j]<minCost && lowCost[j]>0){minCost = lowCost[j];k = j;}}MinSpanTree curr = new MinSpanTree();curr.vertex = g.getValue(k);//保存弧头结点kcurr.weight = minCost;//保存相应权值closeVertex[i] = curr;lowCost[k] = -1;//标记结点k//根据加入集合U的结点k修改lowCost中的数值//也就是求结点0和k与其他不在U中的结点之间的连接权值//取0和k到同一个结点想不最小的权值for(int j=1;j<n;j++){if(g.getWeight(k, j)<lowCost[j])lowCost[j] = g.getWeight(k, j);}}}}

主函数测试:

package Map;/*** @author sun* 创建时间:2017年5月10日下午3:33:04*/public class TestPrim {static final int maxVertices = 100;//构造图public static void createGraph(AdjMWGraph g,Object[] v,int n,RowColWeight[] rc,int e)throws Exception{for(int i=0;i<n;i++)g.insertVertex(v[i]);for(int k=0;k<e;k++)g.inserEdge(rc[k].row,rc[k].col, rc[k].weight);}public static void main(String[] args) {AdjMWGraph g = new AdjMWGraph(maxVertices);Character[] a = {new Character('A'),new Character('B'),new Character('C'),new Character('D'),new Character('E'),new Character('F'),new Character('G')};//注意无向图的邻接矩阵对称性RowColWeight[] rcw = {new RowColWeight(0,1,50),new RowColWeight(1,0,50),new RowColWeight(0,2,60),new RowColWeight(2,0,60),new RowColWeight(1,3,65),new RowColWeight(3,1,65),new RowColWeight(1,4,40),new RowColWeight(4,1,40),new RowColWeight(2,3,52),new RowColWeight(3,2,52),new RowColWeight(2,6,45),new RowColWeight(6,2,45),new RowColWeight(3,4,50),new RowColWeight(4,3,50),new RowColWeight(3,5,30),new RowColWeight(5,3,30),new RowColWeight(3,6,42),new RowColWeight(6,3,42),new RowColWeight(4,5,70),new RowColWeight(5,4,70),};int n = 7,e = 20;try{createGraph(g,a,n,rcw,e);MinSpanTree[] closeVertex = new MinSpanTree[7];Prim.prim(g, closeVertex);//输出prim函数得到的最小生成树的结点序列和权值序列System.out.println("初始顶点 = "+closeVertex[0].vertex);for(int i=1;i<n;i++){System.out.print("顶点 = "+closeVertex[i].vertex);System.out.println("边的权值 = "+closeVertex[i].weight);}}catch(Exception ex){ex.printStackTrace();}}}/*初始顶点 = A顶点 = B边的权值 = 50顶点 = E边的权值 = 40顶点 = D边的权值 = 50顶点 = F边的权值 = 30顶点 = G边的权值 = 42顶点 = C边的权值 = 45*/

Kruskal算法:

这是一种按照带权图中边的权值的递增顺序构造最小生成树的方法。

无向连通带权图G=(V,E),设带权图G的最小生成树T由结点集合和边的集合构成,其初值为T=(V,{ }),即初始时,T只由带权图G中的所有结点构成,各结点之间没有连接。这样,树T中的各个结点各自构成一个连通分量。

然后,按照权值递增的顺序考察图G的边集合E中的各条边,若被考察的边的两个结点属于T的两个不同的连通分量,则将此边加入到树T,同时把这两个连通分量连接为一个连通分量;

若被考察的边的两个结点属于T的同一个连通分量,则将此边舍去。

如此下去,当T中的连通分量个数为1时,T中的该连通分量即为最小生成树。

Kruskal算法主要包括两部分:(1)对图G中各条边的权值排序;(2)判断选择的边的两个结点是否属于同一个连通分量。

所以,该算法的时间复杂度主要由排序方法决定,而排序算法只与边的个数有关,与结点个数无关。

总结:当带权图的结点个数较多,而边的条数较少时,使用Kruskal算法构造最小生成树效果较好。

0 0
原创粉丝点击