7.4图的连通性

7.4图的连通性

1. 无向图的连通分量

在对图遍历时,对于连通图,无论是广度优先搜索还是深度优先搜索,仅需要调用一次搜索过程,即从任一个顶点出发,便可以遍历图中的各个顶点。对于非连通图,则需要多次调用搜索过程,而每次调用得到的顶点访问序列恰为各连通分 量中的顶点集。例如,下图(a)是一个非连通图,按照它的邻接表进行深度优先搜 索遍历,三次调用 DepthFirstSearch 过程得到的访问顶点序列为:
在这里插入图片描述
可以利用图的遍历过程来判断一个图是否连通。如果在遍历的过程中,不止一次调用搜索过程,则说明该图就是一个非连通图。几次调用搜索过程,表明该图就有几个连通分量。

2.图中两个顶点之间的简单路径

在图的应用问题中,常常需要找从顶点 u 到另一个顶点 v 的简单路径,即路 径中的顶点均不相同。u 到 v 可能存在多条简单路径,由于遍历过程将走遍图中 的所有顶点,故可以在深度(或广度)优先搜索算法基础上加以适当的条件,就 能得到求解此问题的算法,因此可以将此问题看成是有条件的图遍历过程。

算法思想
从顶点 u 开始,进行深度(或广度)优先搜索,如果能够搜索 到顶点 v,则表明从顶点 u 到顶点 v 有一条路径。由于在搜索过程中,每个顶点 只访问一次,所以这条路径必定是一条简单路径。因此,只要在搜索过程中,把 搜索的线路记录下来,并在搜索到顶点 vj 时退出搜索过程,这样就可得到从 u 到 v 的一条简单路径。

为了记录搜索线路,需要设置一个数组 pre[n],当从某个顶vi找到其邻接顶 点vj进行访问时,将 pre[j]置为 i , 即:pre[j]=i 。 这样,当退出搜索后,就能根据 pre 数组从顶点 v 追溯到顶点 u,从而输出这条从 u 到 v 的简单路径。在具体设 计这一算法时,可以顺便用 pre 数组替代 visited 数组,用 pre[j]=-1 表示 vj未被访问,pre[j]!=-1 表示 vj已被访问。

根据上面的思路,对深度优先搜索算法进行修改,就可得到一个求 u 到 v 的 简单路径的算法。

算法描述】 深度优先找出从顶点 u 到 v 的简单路径

int *pre; void one_path(Graph *G, int u, int v) /*在连通图 G 中找一条从第 u 个顶点到 v 个顶点的简单路径*/ 
{
	int i;     
	pre=(int *)malloc(G->vexnum*sizeof(int));     
	for(i=0;i<G->vexnum;i++) pre[i]=-1;     
		pre[u]=u;               /*将 pre[u]置为非-1,表示第 u 个顶点已被 访问*/     
	DFS_path(G, u, v);       /*用深度优先搜索找一条从u到v的简单路径。 */     
	free(pre); 
} 
void DFS_path(Graph *G, int u, int v)   /*在连通图 G 中用深度优先搜索策略找一条从 u 到 v 的简单路径。*/ 
{
	int j;      
	if(pre[v]!=-1)         
		for(j=firstadj(G,u);
			j>=0;
		j=nextadj(G,u,j))              
		if(pre[j]==-1) 
		{                   
			pre[j]=u;                   
			if(j==v) 
				print_path(pre ,v); /*输出路径*/                  
			else 
				DFS_path(G,j,v);                
		} 
}  

3. 图的生成树与最小生成树

(1)生成树

一个连通图的生成树是指一个极小连通子图,它含有图中的全部顶点,但只 有足已构成一棵树的n-1条边,如图7.5所示。如果在一棵生成树上添加一条边, 必定构成一个环,这是因为该条边使得它依附的两个顶点之间有了第二条路经。 一棵有 n 个顶点的生成树有且仅有 n-1 条边,如果它多于 n-1 条边,则一定有回路。但是,有 n-1 条边的图并非一定连通,不一定存在生成树。如果一个图有 n 个顶点且边数小于 n-1 条,则该图一定是非连通图。

(2)最小生成树

在一个连通网的所有生成树中,各边的代价之和最小的那棵生成树称为该连 通网的最小代价生成树(Minimum Cost Spanning Tree),简称为最小生成树(MST)

最小生成树有如下重要性质:
设 N=(V,{E}) 是一个连通网,U 是顶点集 V 的一个非空子集。若(u , v)是 一条具有最小权值的边,其中 u∈U,v∈V-U,则存在一棵包含边(u , v)的最小 生成树。

可以用反证法来证明这个 MST 性质:
假设不存在这样一棵包含边(u , v)的最小生成树。任取一棵最小生成树 T, 将(u , v)加入 T 中。根据树的性质,此时 T 中必形成一个包含(u , v)的回路, 且回路中必有一条边(u’ , v’)的权值大于或等于(u , v)的权值。删除(u’ , v’), 则得到一棵代价小于等于 T 的生成树 T’,且 T’为一棵包含边(u , v)的最小生成 树。这与假设矛盾,故该性质得证。 可以利用 MST 性质来生成一个连通网的最小生成树。普里姆(Prim)算法和克 鲁斯卡尔(Kruskal)算法便是利用了这个性质。

下面分别介绍这两种算法。

(1)普里姆算法

假设 N=(V,{E})是连通网,TE 为最小生成树中边的集合。

  • ①初始 U={u0}(u0∈V),TE=φ;
  • ②在所有 u∈U, v∈V-U 的边中选一条代价最小的边(u0,v0)并入集合 TE, 同时将 v0并入 U;
  • ③重复(2), 直到 U=V 为止。

此时,TE 中必含有 n-1 条边,则 T=(V,{TE})为 N 的最小生成树。

下图给出了一个连通网,以及从顶点 V1 开始构造最小生成树的例子。可以 看出,普里姆算法逐步增加 U 的中顶点,可称为“加点法”。
注意:选择最小边时,可能有多条同样权值的边可选,此时任选其一。

为了实现这个算法需要设置一个辅助数组 closedge[ ],以记录从 U 到 V-U 具 有最小代价的边。

对每个顶点 v∈V-U,closedge[v]记录所有与 v 邻接的、从 U 到 V-U 的那组边 中的最小边信息。closedge[v]包括两个域:adjvex 和 lowcost,其中 lowcost 存储 最小边的权值,adjvex 记录最小边在 U 中的那个顶点。

显然有 closedge[v].lowcost=Min({cost(u,v) | u∈U})

算法思想

  • ①首先将初始顶点 u 加入 U 中,对其余的每一个顶点 i,将 closedge[i]均初始化为到 u 的边信息;
  • ②循环 n-1 次,做如下处理:
    a) 从各组最小边 closedge[v]中选出最小的最小边 closedge[k0];( v, k0∈V-U)
    b) 将 k0 加入 U 中;
    c) 更新剩余的每组最小边信息 closedge[v]:( v∈V-U)
    对于以 v 为中心的那组边,新增加了一条从 k0 到 v 边,如果新边的权值比 closedge[v].lowcost 小,则将 closedge[v].lowcost 更新为新边的权值。

算法描述】 普里姆算法

struct 
{     
	VertexData  adjvex;     
	int   lowcost; 
}closedge[MAX_VERTEX_NUM];   /* 求最小生成树时的辅助数组*/ 

MiniSpanTree_Prim(AdjMatrix  gn,  VertexData  u) /*从顶点 u 出发,按普里姆算法构造连通网 gn 的最小生成树,并输出生成树的 每条边*/ 
{ 
	k=LocateVertex(gn, u); 
	closedge[k].lowcost=0;   /*初始化,U={u} */ 
	for (i=0; i<gn.vexnum; i++)     
		if ( i!=k)    /*对 V-U 中的顶点 i,初始化 closedge[i]*/ 
		{ 
			closedge[i].adjvex=u;  
			closedge[i].lowcost=gn.arcs[k][i].adj; 
		} 
		for (e=1; e<=gn.vexnum-1; e++)    /*找 n-1 条边(n= gn.vexnum) */ 
		{ 
			k0=Minium(closedge);     /* closedge[k0]中存有当前最小边(u0,v0)的 信息*/
			u0= closedge[k0].adjvex;   /* u0∈U*/ 
			v0= gn.vertex[k0]          /* v0∈V-U*/         
			printf(u0, v0);    /*输出生成树的当前最小边(u0,v0)*/         
			closedge[k0].lowcost=0;     /*将顶点 v0 纳入 U 集合*/         
			for ( i=0 ;i<vexnum;i++)    /*在顶点 v0 并入 U 之后,更新 					closedge[i]*/           
				if ( gn.arcs[k0][i].adj <closedge[i].lowcost)              
				{ 
					closedge[i].lowcost= gn.arcs[k0][i].adj;                
					closedge[i].adjvex=v0;           
				}   
		}   
}   

由于算法中有两个 for 循环嵌套,故它的时间复杂度为 O(n²)。

在这里插入图片描述

(2)克鲁斯卡尔算法

假设 N=(V,{E})是连通网,将 N 中的边按权值从小到大的顺序排列;

  • ① 将 n 个顶点看成 n 个集合;
  • ② 按权值由小到大的顺序选择边,所选边应满足两个顶点不在同一个顶点集 合内,将该边放到生成树边的集合中。同时将该边的两个顶点所在的顶点 集合合并;
  • ③ 重复②直到所有的顶点都在同一个顶点集合内。

可以看出,克鲁斯卡尔算法逐步增加生成树的边,与普利姆算法相比,可称 为“加边法”。

以下图(a)中的连通网,详细说明克鲁斯卡尔算法的执行过程。
在这里插入图片描述

设 N= (V, {E}),最小生成树的初态为 T=(V, { })。

  • (1)待选的边: (2,3)->5 , (2,4)->6 , (3,4)->6 , (2,6)->11 , (4,6)->14 , (1,2)->16 , (4,5)->18 , (1,5)->19, (1,6)->21 , (5,6)->33。 顶点集合状态:{1},{2},{3},{4},{5},{6} 最小生成树的边的集合: { }。
  • (2)从待选边中选一条权值最小的边为: (2,3)->5。
    待选的边变为:(2,4)->6 , (3,4)->6 , (2,6)->11 , (4,6)->14 , (1,2)->16 , (4,5)->18 , (1,5)->19, (1,6)->21 , (5,6)->33。
    顶点集合状态变为: {1},{2,3},{4},{5},{6}。
    最小生成树的边的集合:{(2,3)}。
  • (3)从待选边中选一条权值最小的边为: (2,4)->6;
    待选的边变为:(3,4)->6 , (2,6)->11 , (4,6)->14 , (1,2)->16 , (4,5)->18 , (1,5)->19, (1,6)->21 , (5,6)->33。
    顶点集合状态变为:{1},{2,3,4},{5},{6}。
    最小生成树的边的集合 {(2,3),(2,4)}。
  • (4)从待选边中选一条权值最小的边为:(3,4)->6,由于 3、4 在同一个顶点 集合{2,3,4}内,故放弃。
    重新从待选边中选一条权值最小的边 为:(2,6)->11。
    待选的边变为:(4,6)->14 , (1,2)->16 , (4,5)->18 , (1,5)->19, (1,6)->21 , (5,6)->33。
    顶点集合状态变为: {1},{2,3,4,6},{5}。
    最小生成树的边的集合 {(2,3),(2,4),(2,6)}。
  • (5)从待选边中选一条权值最小的边为:(4,6)->14,由于 4、6 在同一个顶点 集合{2, 3, 4, 6}内,故放弃。重新从待选边中选一条权值最小的边为:(1,2)->16。
    待选的边变为:(4,5)->18 , (1,5)->19, (1,6)->21 , (5,6)->33。
    顶点集合状态变为: {1,2,3,4,6},{5}。
    最小生成树的边的集合: {(2,3),(2,4),(2,6),(1,2)}。
  • (6)从待选边中选一条权值最小的边为: (4,5)->18。
    待选的边变为: (1,5)->19, (1,6)->21 , (5,6)->33。
    顶点集合状态变为: {1,2,3,4,6,5}。
    最小生成树的边的集合 {(2,3),(2,4),(2,6),(1,2),(4,5)}。 至此,所有的顶点都在同一个顶点集合{1,2,3,4,6,5}里,算法结束。
    所得最小生成树如下图所示,其代价为:5+6+11+16+18=56。
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值