图的连通性
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
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); } }
/* 求最小生成树时的辅助数组*/
MiniSpanTree_Prim(AdjMatrix gn, VertexData u)
/*从顶点 u 出发,按普里姆算法构造连通网 gn 的最小生成树,并输出生成树的
每条边*/
{
k=LocateVertex(gn, u);
closedge[k].lowcost=0; /*初始化,U={u} */
(V1,V3),(V4,V6),(V2,V5),(V3,V6),(V2,V3)
在选择第五条边时,因为 V1、V4 已经在同一集合内,如果选(V1,V4),
则会形成回路,所以选(V2,V3)。
以下图(a)中的连通网,详细说明克鲁斯卡尔算法的执行过程。
克鲁斯卡尔算法执行示意图
设 N= (V, {E}),最小生成树的初态为 T=(V, { })。 (1)待选的边:
Closedge[i 0 1 2 3 4 5
U
V-U
0 v0)
]
adjvex lowcost
V V1 V1 V1 V1
0
1 6
1
5 ∞∞
{V1}
{V2,V3,V4, V5,V6}
1
2
(V1, V3)
adjvex
V3
V1 V3 V3
lowcost 0 5 0 5 6 4
{V1,V3}
{V2,V4,V5, V6}
径中的顶点均不相同。u 到 v 可能存在多条简单路径,由于遍历过程将走遍图中
的所有顶点,故可以在深度(或广度)优先搜索算法基础上加以适当的条件,就
能得到求解此问题的算法,因此可以将此问题看成是有条件的图遍历过程。
【算法思想】从顶点 u 开始,进行深度(或广度)优先搜索,如果能够搜索
到顶点 v,则表明从顶点 u 到顶点 v 有一条路径。由于在搜索过程中,每个顶点
2
5
(V3, V6)
adjvex lowcost
V3
V6 V3
05 0 2 6 0
{V1,V3,V6}
{V2,V4,V5 }
33
(V6, V4)
adjvex
V3
V3
{V1,V3,V6,
lowcost 0 5 0 0 6 0
V4}
{V2,V5} 4 1 (V3, V2)
adjvex
V2
{V1,V3,V6,
第 4 讲 图的连通性——内容简介 利用遍历算法求解图的连通性问题,并讨论最小代价生成树的算法。
1. 无向图的连通分量
在对图遍历时,对于连通图,无论是广度优先搜索还是深度优先搜索,仅需要调
用一次搜索过程,即从任一个顶点出发,便可以遍历图中的各个顶点。对于非连
通图,则需要多次调用搜索过程,而每次调用得到的顶点访问序列恰为各连通分
lowcost 0 0 0 0 3 0 V4,V2}
adjvex
{V1,V3,V6,V
lowcost 0 0 0 0 0 0 4,V2,V5}
{V5} 5 4 (V2, V5)
{}
(2)克鲁斯卡尔算法 假设 N=(V,{E})是连通网,将 N 中的边按权值从小到大的顺序排列; ① 将 n 个顶点看成 n 个集合; ② 按权值由小到大的顺序选择边,所选边应满足两个顶点不在同一个顶点集 合内,将该边放到生成树边的集合中。同时将该边的两个顶点所在的顶点 集合合并; ③ 重复②直到所有的顶点都在同一个顶点集合内。 可以看出,克鲁斯卡尔算法逐步增加生成树的边,与普利姆算法相比,可称
为“加边法”。
例如,对于图 7.18 所示的连通网,将所有的边按权值从小到大的顺序排列为:
权值 1
2
3
4
5
5
5
6
6
6
边 (V1,V3) (V4,V6) (V2,V5) (V3,V6) (V1,V4) (V2,V3) (V3,V4) (V1,V2)
(V3,V5) (V5,V6)
经过筛选所得到边的顺序为:
最小生成树有如下重要性质: 设 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 中的那个顶点。显然有
只访问一次,所以这条路径必定是一条简单路径。因此,只要在搜索过程中,把
搜索的线路记录下来,并在搜索到顶点 vj 时退出搜索过程,这样就可得到从 u 到 v 的一条简单路径。
为了记录搜索线路,需要设置一个数组 pre[n],当从某个顶 vi 找到其邻接顶 点 vj 进行访问时,将 pre[j]置为 i , 即:pre[j]=i 。 这样,当退出搜索后,就能根 据 pre 数组从顶点 v 追溯到顶点 u,从而输出这条从 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;
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) */
计这一算法时,可以顺便用 pre 数组替代 visited 数组,用 pre[j]=-1 表示 vj 未被访 问,pre[j]!=-1 表示 vj 已被访问。
根据上面的思路,对深度优先搜索算法进行修改,就可得到一个求 u 到 v 的
简单路径的算法。
【算法描述】 深度优先找出从顶点 u 到 v 的简单路径
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;
量中的顶点集。例如,下图(a)是一个非连通图,按照它的邻接表进行深度优先搜 索遍历,三次调用 DepthFirstSearch 过程得到的访问顶点序列为:
1,2,4,3,9 5,6,7 8,10
A
2
3
A
B
B
1
4
9
F
J
C
1
D
2பைடு நூலகம்
4
3
9
E
G
H
IE
6
7
F
5
G
5
H
10
C
D
I
2
4
J
8
(a) 无向图 G5
A
B
I
(b) G5 的邻接表
J
F
E
H
G
C
D
(c) 无向图 G5 的三个连通分量
图及其连通分量
可以利用图的遍历过程来判断一个图是否连通。如果在遍历的过程中,不止一次 调用搜索过程,则说明该图就是一个非连通图。几次调用搜索过程,表明该图就 有几个连通分量。
2.图中两个顶点之间的简单路径
在图的应用问题中,常常需要找从顶点 u 到另一个顶点 v 的简单路径,即路
closedge[i].adjvex=v0;
}
}
}
由于算法中有两个 for 循环嵌套,故它的时间复杂度为 O(n2)。 利用该算法,对下图(a)所示的连通网从顶点 V1 开始构造最小生成树,算
法中各参量的变化如表 7-1 所示
普里姆算法构造最小生成树的过程 表 7-1 普里姆算法各参量的变化
i
e k (u0,
对于以 v 为中心的那组边,新增加了一条从 k0 到 v 边,如果新边的权值 比 closedge[v].lowcost 小,则将 closedge[v].lowcost 更新为新边的权值。
【算法描述】 普里姆算法
struct {
VertexData adjvex;
int
lowcost;
} closedge[MAX_VERTEX_NUM];
3. 图的生成树与最小生成树
(1)生成树 一个连通图的生成树是指一个极小连通子图,它含有图中的全部顶点,但只
有足已构成一棵树的 n-1 条边,如图 7.5 所示。如果在一棵生成树上添加一条边, 必定构成一个环,这是因为该条边使得它依附的两个顶点之间有了第二条路经。 一棵有 n 个顶点的生成树有且仅有 n-1 条边,如果它多于 n-1 条边,则一定有回 路。但是,有 n-1 条边的图并非一定连通,不一定存在生成树。如果一个图有 n 个顶点且边数小于 n-1 条,则该图一定是非连通图。
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)
{
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)*/
思考题:用深度优先搜素或广度优先搜素思想,可得到图对应的深度优先生 成树和广度优先生成树,生成这两种生成树时,需要对遍历算法算法做哪些修改 补充。 (2)最小生成树
在一个连通网的所有生成树中,各边的代价之和最小的那棵生成树称为该连 通网的最小代价生成树(Minimum Cost Spanning Tree),简称为最小生成树(MST)。