7.3 图-最小生成树

图-最小生成树

  • 1 普里姆算法
  • 2 克鲁斯卡尔算法
  • 3 重(双)连通图和关节点
  • 4 两点之间的最短路径问题
    • 4.1 源点到其余各点的最短路径
    • 4.2 每一对顶点之间的最短路径
  • 5 拓扑排序
  • 6 关键路径
  • 待处理

问题:如图假设要在 n 个城市之间建立通讯联络网,则连通 n 个城市只需要修建 n-1 条线路,如何在最节省经费的前提下建立这个通讯网?

该问题等价于:构造网的一棵最小生成树,即:在 e 条带权的边中选取 n-1条 (不构成回路),使 “权值之和” 为最小。
7.3 图-最小生成树_第1张图片

最小代价生成树(Minimum Cost Spanning Tree)(简称为最小生成树)。
一棵生成树的代价就是树上各边的代价之和。
构造最小生成树可以有多种算法。其中多数算法利用了最小生成树的下列一种简称为 M S T MST MST 的性质:假设 N = ( V ,   { E } ) N=(V,\ \{E\}) N=(V, {E}) 是一个连通网, U U U 是顶点集 V V V 的一个非空子集。若 ( u ,   v ) (u,\ v) (u, v) 是一条具有最小权值(代价)的边,其中 u ∈ U u\in U uU v ∈ V − U v\in V-U vVU ,则必存在一棵包含边 ( u ,   v ) (u,\ v) (u, v) 的最小生成树。

1 普里姆算法

假设 N = ( V ,   { E } ) N=(V,\ \{E\}) N=(V, {E}) 是连通网, T E TE TE N N N 上最小生成树中边的集合。
算法从 U = {   u 0   }    ( u 0 ∈ V ) U=\{\ u_{0}\ \}\ \ (u_{0} \in V) U={ u0 }  (u0V) T E = {   } TE=\{\ \} TE={ } 开始,重复执行下述操作:在所有 u ∈ U u\in U uU v ∈ V − U v \in V-U vVU 的边 ( u ,   v ) ∈ E (u,\ v )\in E (u, v)E 中找一条代价最小的边 ( u 0 ,   v 0 ) (u_{0},\ v_{0}) (u0, v0) 并入集合 T E TE TE,同时 v 0 v_{0} v0 并入 U U U,直至 U = V U=V U=V 为止。此时 T E TE TE 中必有 n − 1 n-1 n1 条边,则 T = ( V ,   { T E } ) T=(V,\ \{TE\}) T=(V, {TE}) N N N 的最小生成树。

可取图中任意一个顶点v作为生成树的根,之后若要往生成树上添加顶点w,则在顶点v和顶点w之间必定存在一条边,并且该边的权值在所有连通顶点v和w之间的边中取值最小。
一般情况下,假设n个顶点分成两个集合:U (包含已落在生成树上的顶点)和 V-U (尚未落在生成树上的顶点),则在所有连通U中顶点和V-U中顶点的边中选取权值最小的边。
7.3 图-最小生成树_第2张图片
7.3 图-最小生成树_第3张图片

假设以二维数组表示网的邻接矩阵,且令两个顶点之间不存在的边的权值为机内允许的最大值(INT_MAX),则普里姆算法如下所示:

//此代码图的存储结构采用的是邻接矩阵
void MiniSpanTree_PRIM(MGraph G, VertexType u){
	//用普里姆算法从第u个顶点出发构造网G的最小生成树T,输出T的各条边。
	//记录从顶点集U到V-U的代价最小的边的辅助数组定义:
	//struct {
	//	VertexType adjvex;
	//	VRType lowcost;
	//}closedge[MAX_VERTEX_NUM];
	k = LocateVex(G, u);
	for(j=0; j<G.vexnum; ++j){ //辅助数组初始化
		if(j!=k){
			closedge[j] = {u, G.arc[k][i].adj}; //{adjvex, lowcost}
		}
	}
	closedge[k].lowcost = 0; //初始 U = {u}
	for(i=1; i<G.vexnum; ++i){ //选择其余G.vexnum-1个顶点
		k = minimum(closedge); //求出T的下一个结点;第k顶点

		//此时 closedge[k].lowcost = MIN{closedge[vi].lowcost | closedge[vi].lowcost>0, vi 属于 V-U )
		printf(closedge[k].adjvex, G.vexs[k]); //输出生成树的边
		closedge[k].lowcost = 0; //第k顶点并入U集

		for(j=0; j<G.vexnum; ++j){
			if(G.arcs[k][j].adj < closedge[j].lowcost){ //新顶点并入U后重新选择最小边
				closedge[j] = {G.vexs[k], G.arcs[k][j].adj};
			}
		}
	}
} //MiniSpanTree_PRIM

2 克鲁斯卡尔算法

假设连通网 N = ( V ,   { E } ) N=(V,\ \{E\}) N=(V, {E}) ,则令最小生成树的初始状态为只有 n n n 个顶点而无边的非连通图 T = ( V ,   {   } ) T=(V,\ \{\ \}) T=(V, { }) ,图中每个顶点自成一个连通分量。在 E E E 中选择代价最小的边,若该边依附的顶点落在 T T T 中不同的连通分量上,则将此边加入到 T T T 中,否则舍去此边而选择下一条代价最小的边。依次类推,直至 T T T 中所有顶点都在同一连通分量上为止。

为使生成树上边的权值之和最小,显然,其中每一条边的权值应该尽可能地小。克鲁斯卡尔算法的做法就是:先构造一个只含 n 个顶点的子图 SG ,然后从权值最小的边开始,若它的添加不使 SG 中产生回路,则在 SG 上加上这条边,如此重复,直至加上 n-1 条边内止。

算法:
构造非连通图 S T = ( V ,   {    } ) ; ST=(V,\ \{\ \ \}); ST=(V, {  });
        k=i=0;
        while(k                 ++i;
                //从边集E中选取第i条权值最小的边(u,v);
                //若(u,v)加入ST后不使ST中产生回路,
                //则输出边(u,v),且k++;
        }

7.3 图-最小生成树_第4张图片

假设连通网 N = ( V ,   { E } ) N=(V,\ \{E\}) N=(V, {E}) ,则令最小生成树的初始状态为只有 n 个顶点而无边的非连通图 T = ( V ,   { } ) T=(V,\ \{ \}) T=(V, {}) ,图中每个顶点自成一个连通分量。在 E E E 中选择代价最小的边,若该边依附的顶点落在T中不同的连通分量上,则将此边加人到 T T T 中,否则舍去此边而选择下一条代价最小的边。依次类推,直至 T T T 中所有顶点都在同一连通分量上为止。

由于普里姆算法的时间复杂度为 O ( n 2 ) O(n^2) O(n2),则适于稠密图;
克鲁斯卡尔算法需对 e 条边按权值进行排序,其时间复杂度为 O ( e l o g 2 e ) O(elog_{2}e) O(elog2e),则适于稀疏图.

3 重(双)连通图和关节点

问题:若从一个连通图中删去任何一个顶点及其相关联的边,它仍为一个连通图的话,则该连通图被称为重(双)连通图

若连通图中的某个顶点和其相关联的边被删去之后,该连通图被分割成两个或两个以上的连通分量,则称此顶点为关节点

没有关节点的连通图为重(双)连通图

关节点的特征:
假设从某个顶点 V 0 V_{0} V0 出发对连通图进行深度优先搜索遍历,则可得到一棵深度优先生成树,树上包含图的所有顶点。

若生成树的根结点,有两个或两个以上的分支,则此顶点(生成树的根)必为关节点;
对生成树上的任意一个“顶点”,若其某棵子树的根或子树中的其它“顶点”没有和其祖先相通的回边,则该“顶点”必为关节点。

7.3 图-最小生成树_第5张图片

7.3 图-最小生成树_第6张图片
7.3 图-最小生成树_第7张图片
7.3 图-最小生成树_第8张图片
7.3 图-最小生成树_第9张图片

4 两点之间的最短路径问题

4.1 源点到其余各点的最短路径

7.3 图-最小生成树_第10张图片

7.3 图-最小生成树_第11张图片
7.3 图-最小生成树_第12张图片

7.3 图-最小生成树_第13张图片
7.3 图-最小生成树_第14张图片
7.3 图-最小生成树_第15张图片

4.2 每一对顶点之间的最短路径

7.3 图-最小生成树_第16张图片

7.3 图-最小生成树_第17张图片

5 拓扑排序

6 关键路径

待处理

7.3 图-最小生成树_第18张图片

——《数据结构图 (C语言版) 严蔚敏》 学习笔记

你可能感兴趣的:(数据结构与算法,最小生成树,普里姆算法,克鲁斯卡尔算法)