浅谈无向图的连通性
连通图是无向图的一个概念:
- 在无向图中,若从顶点 v 1 v_1 v1 到顶点 v 2 v_2 v2 有路径,则称顶点 v 1 v_1 v1 与 v 2 v_2 v2 是连通的;
- 如果图中任意一对顶点都是连通的,则称此无向图是连通图。
对于一个非连通图,可以通过深度优先搜索或广度优先搜索来获取它的连通分量:从一个未访问过的节点开始进行深度优先或者广度优先搜索,其能到达的所有顶点及其相关的边构成的子图就是一个连通分量;直到访问完全部的节点,便可以获取该图的所有连通分量。
有向图强连通性的概念
在有向图 G = ( V , E ) G=(V,E) G=(V,E) 中,若对于每一对顶点 v 1 v_1 v1 和 v 2 v_2 v2,都存在一条从 v 1 v_1 v1 到 v 2 v_2 v2 和从 v 2 v_2 v2 到 v 1 v_1 v1 的路径,则称此图是强连通图。
相应地有 强连通分量(极大强连通子图) 的概念:设 E i ( 1 ≤ i ≤ r ) E_i(1\leq i \leq r) Ei(1≤i≤r) 是头、尾均在 V i V_i Vi 中的边集,则子图 G i ( V i , E i ) G_i(V_i,E_i) Gi(Vi,Ei)称为 G G G 的一个强连通分量,简称强分量、强支。
强连通图只有一个强连通分量,即其自身;非强连通的有向图有多个强连通分量。
如上图可以分为 5 个强连通分量。
设从 v v v 可到达(以 v v v 为起点的所有有向路径的终点)的顶点集合为 T 1 ( G ) T_1(G) T1(G),而到达 v v v(以 v v v 为终点的所有有向路径的起点)的顶点集合为 T 2 ( G ) T_2(G) T2(G),则包含 v v v 的强连通分量的顶点集合是: T 1 ( G ) ∩ T 2 ( G ) T_1(G)∩T_2(G) T1(G)∩T2(G)。
强连通图的性质定理
定理:一个有向图是强连通的,当且仅当 G G G 中有一个回路,它至少包含每个顶点一次。
- 充分性:如果 G G G 中有一个回路,它至少包含每个顶点一次,则 G G G 中任两个顶点都是互相可达的,故 G G G 是强连通图。
- 必要性:如果有向图是强连通的,则任两个顶点都是相互可达,故必可做一回路经过图中所有顶点。若不然则必有一回路不包含某一顶点 v v v,并且 v v v 与回路上的各顶点就不是相互可达,与强连通条件矛盾。
求有向图强连通分量—Korasaju算法
在计算科学中,Kosaraju的算法(又称为 Sharir Kosaraju算法)是一个线性时间(linear time)算法找到的有向图的强连通分量。它利用了一个事实,逆图(与各边方向相同的图形反转, transpose graph)有相同的强连通分量的原始图。
算法步骤:
- (1)深度优先遍历 G G G(起点如何选择无所谓),并计算出 每 个 顶 点 u 的 结 束 时 间 d f n [ u ] \color{red}{每个顶点 u 的结束时间 dfn[u]} 每个顶点u的结束时间dfn[u](即按递归写法出系统栈的顺序编号,完成时间,这个编号和深度优先遍历序列是不同的);
- (2)深度优先遍历 G G G 的转置(反向)图 G T G^T GT,选择遍历的起点时,按照顶点的结束时间从大到小进行。遍历的过程中,一边遍历,一边给顶点做分类标记,每找到一个新的起点,分类标记值就加 1。
- (3)第(2)步中产生的标记值相同的顶点构成深度优先森林中的一棵树,也即一个强连通分量。
这里提出了 每 个 顶 点 的 结 束 时 间 \color{red}{每个顶点的结束时间} 每个顶点的结束时间 这个概念,这个编号和深度优先搜索的编号是不同的,在拓扑排序的 DFS实现(递归)中也用到了每个顶点的结束时间。
在上图中,图
(
a
)
(a)
(a) 表示有向图
G
G
G,要求其强连通分量:
- 假设从 ( a ) (a) (a) 开始进行深度优先搜索(其实选哪个点都无所谓,最后得到的强连通分量是相通的),记录每个顶点的结束时间,结束时间如 ( b ) (b) (b) 所示;
- 然后对
G
G
G 进行反向得到
G
T
G^T
GT,按上一步得到的结束时间从大到小再对
G
T
G^T
GT 进行深度优先搜索(普通的深度优先搜索):
- 先从结束时间最晚的 a a a 开始搜,可以搜索到 a , d , c a,d,c a,d,c 三个节点,所以将 a , d , c a,d,c a,d,c 最为一个强连通分量中的节点;
- 目前未访问的且结束时间最晚的节点是 b b b,所以再从 b b b 开始对 G T G^T GT 进行深度优先搜索,可以搜到 b , e , f b,e,f b,e,f,所以将 b , e , f b,e,f b,e,f 作为一个强连通分量中的节点;
- 至此,图 G T G^T GT 中没有未访问的节点,算法结束,共两个强连通分量,如 ( d ) (d) (d) 所示。
伪代码:
/* 按弧的正向搜索,起点如何选择无所谓 */
int in_order[MAX_VEX] ;
void DFS(OLGraph *G , int v) // 求每个节点的结束时间
{
ArcNode *p;
Count = 0;
visited[v] = TRUE;
for(p=G->xlist[v].firstout; p!=NULL; p=p->tlink)
if(!visited[p->headvex])
DFS(G, p->headvex);
in_order[count++]=v; // 出系统栈的顺序,不是真的深度优先搜索序列
}
/* 对图G按弧的逆向进行搜索 */
void Rev_DFS(OLGraph *G , int v)
{
ArcNode *p;
visited[v] = TRUE;
printf(“%d”, v); /* 输出顶点 */
for(p=G->xlist[v].firstin; p!=NULL; p=p->hlink)
if(!visited[p->tailvex])
Rev_DFS(G, p->tailvex);
}
void Strongly_Connected_Component(OLGraph *G)
{
int k = 1, v, j;
/* 对图G正向遍历 */
for (v=0; v<G->vexnum; v++)
visited[v] = FALSE;
for (v=0; v<G->vexnum; v++)
if(!visited[v])
DFS(G, v);
/* 对图G逆向遍历 */
for (v=0; v<G->vexnum; v++)
visited[v] = FALSE;
for (j=G->vexnum-1; j>=0; j--) {
v = in_order[j];
if (!visited[v]) {
// 从这个点开始的深度优先搜索能搜到的节点都属于一个连通分量
printf(“\n第%d个连通分量顶点: ”, k++);
Rev_DFS(G, v);
}
}
}