最小生成树问题(Minimum Spanning Tree)——图
-
1、最小生成树(Minimum Spanning Tree)
2、最小生成树的典型用途
3、最小生成树的求解
·普里姆算法(Prim)
·克鲁斯卡尔算法(Kruskal)
1、最小生成树(Minimum Spanning Tree)
目标:在网的多个生成树中,寻找一个各边权值之和最小的生成树,即最小生成树。构造最小生成树的准则:
1、必须只使用该网中的边来构造最小生成树
2、必须使用且仅使用n-1条边来联结网络中的n个顶点
3、不能使用产生回路的边
2、最小生成树的典型用途
欲在n个城市间建立通信网,则n个城市应铺n-1条线路,但因为每条线路都会有对应的经济成本,而n个城市可能有n(n-1)/2条线路,那么,如何选择n-1条线路,使总费用最少?
数学模型:
顶点:表示城市,有n个
边:表示线路,有n-1条
边和权值:表示线路的经济代价
3、最小生成树的求解
·普里姆算法(Prim)
1、普里姆算法的基本思想:加点法
设N = (V,{E})使连通网,TE是N上最小生成树中边的集合。
Prim算法思想的构造过程:
2、设计数据结构(1)图采用邻接矩阵来存储
(2)一维数组closedeg,记录从U到V-U具有最小代价的边。
/*图的邻接矩阵存储表示法*///用两个数组分别存储顶点表和邻接矩阵#define MaxInt 32767 //表示极大值,即无穷#define MVNum 100 //最大顶点数typedef char VerTexType; //假设顶点的数据类型为字符型typedef int ArcType; //假设边的权值类型为整型typedef struct{ VerTexType vexs[MVNum]; //顶点表 ArcType arcs[MVNum][MVNum]; //邻接矩阵 int vexnum, arcnum; //图的当前顶点数和边数}AMGraph;
对每个顶点v,V-U在辅助数组存在一个相应的分量closedge[i-1],它包括两个域:
typedef struct{ VerTexType adjvex;//最小边的顶点 ArcType lowcost;//最小边的权值}closedge[MAX_VERTEX_NUM];//adjvex:依附于这条最小代价边的另一个顶点//lowcost = 0 :表示顶点已经在顶点集U中//lowcost > 0 :表示顶点i还在V-U中
所以,每次循环须在lowcost >0(在集合V-U中)的那些顶点中选择lowcost最小的顶点加入到集合中,同时将相关顶点的closedge作相应的调整。
3、Prim算法描述
void MiniSpanTree_Prim(AMGraph G, VerTexType u){//无向网G以邻接矩阵存储,从顶点u出发构造G的最小生成树T,输出T的各条边 k = LocateVex(G, u);//起点位置,k为顶点u的下标 for (j = 0;j < G.vexnum;++j)//对V-U的每个顶点vi,初始化closedge[i] { if (j != k) { closedge[j].adjvex = u; closedge[j].lowcost = G.arcs[k][j]; } } closedge[k].lowcost = 0;//初始,U = { u } for (i = 1;i < G.vexnum;++i) {//选择其余n-1个顶点,生成n-1条边(n = G.vexnum ) k = Min(closedge);//求出T的下一个结点:closedge[k]存有当前最小边 u0 = closedge[k].adjvex;//u0为最小边的一个顶点,u0 属于 U v0 = G.vexs[k];//v0为最小边的另一个顶点,v0 属于 V-U cout << "边" << u0 << "-->" << v0 << endl;//输出当前的最小边(u0,v0) closedge[k].lowcost = 0;//第k个顶点并入U集 for(j=0;j if (G.arcs[k][j] < closedge[j].lowcost) {//新顶点并入U后重新选择最小边 closedge[j].adjvex = G.vexs[k]; closedge[j].lowcost = G.arcs[k][j]; } }}
·克鲁斯卡尔算法(Kruskal)
1、克鲁斯卡尔Kruskal算法的基本思想:加边法有n个结点,都看成独个连通分量,在所有边中选取权值最小的边,将两个顶点连成一个连通分量,舍弃两个顶点间的其他连线,重复此步骤,直到所有顶点都在一个连通分量上面为止。
2、设计数据结构算法实现要引入以下数据结构:(1)结构体数组Edge:存储边的信息,包括边的两个顶点信息和权值。
//辅助数组Edges的定义typedef struct{ VerTexType Head;//边的始点 VerTexType Tail;//边的终点 ArcType lowcost;//边上的权值}Edge[arcnum];
(2)、Vexset[i]:标识各个顶点所属的连通分量。对每个顶点vi属于V,在辅助数组中存在一个相应元素Vexset[i]表示该顶点所在的连通分量。初始化时 Vexset[i] = i,表示各顶点自成一个连通分量。
//辅助数组Vexset的定义int Vexset[MVNum];
3、Kruskal算法描述
void MiniSpanTree_Kruskal(AMGraph G){ //无向网G以邻接矩阵形式存储,构造G的最小生成树T,输出T的各条边 Sort(Edge);//将数组Edge中的元素按权值从小到大排序 for (i = 0;i < G.vexnum;++i)//辅助数组,表示各顶点自成一个连通分量 Vexset[i] = i; for (i = 1;i < G.arcnum;++i) { //依次查看排好序的数组Edge中的边是否在同一连通分量上 v1 = LocateVex(G, Edge[i].Head);//v1为边的始点Head的下标 v2 = LocateVex(G, Edge[i].Tail);//v2为边的终点Tail的下标 vs1 = Vexset[v1];//获取边Edge[i]的始点所在的连通分量vs1 vs2 = Vexset[v2];//获取边Edge[i]的终点所在的连通分量vs2 if (vs1 != vs2)//边的两个顶点分属不同的连通分量 { cout << Edge[i].Head << Edge[i].End;//输出此边 for (i = 0;i < G.vexnum;++i)//合并vs1和vs2两个分量,即两个集合统一编号 if (Vexset[i] == vs2) Vexset[i] = vs1;//集合编号为vs2的都改为vs1 } }}
练习:利用Prim算法、Kruskal算法构造最小生成树
答案: