這是DFS系列的第二篇
In graph theory, a bridge, isthmus, cut-edge, or cut arc is an edge of a graph whose deletion increases its number of connected components. Equivalently, an edge is a bridge if and only if it is not contained in any cycle. A graph is said to be bridgeless or isthmus-free if it contains no bridges.
Let $G = (V, E)$ be a connected, undirected graph, a bridge of $G$ is an edge whose removal disconnects $G$. (Introduction to Algorithms p.621)
注意:割邊這一概念只適用於無向圖,不適用於有向圖,因為有向圖的連通性和無向圖的連通性是完全不同的兩個概念。對於某有向圖 $G$,簡單地稱它連通是很不完善的。有向圖的連通性有強連通(strongly connected)和半連通(semiconnected)兩種常見的提法。上面英文描述中的“graph”及下文中的“圖”均指無向圖。
割邊 (cut edge)也稱作橋(bridge)是刪除后能使圖的連通分量增加的邊。
下面我們只考慮沒有重邊的無向圖。
考慮一個連通的無向圖 $G$,若它含有某條割邊 $(u, v)$,那么去掉這條邊后,將得到2個連通圖 $G'$,$H'$,而不可能得到 $2$ 個以上連通圖,因為一條邊最多能將 $2$ 個連通圖合為一個聯通圖。(這句話貌似和上下文無關)
下面介紹求無向圖所有割邊的Tarjan算法(Tarjan's Bridge-Finding Algorithm)
我們只考慮對無向連通圖 $G$ 求割邊,若圖 $G$ 不連通那么就對 $G$ 的各個連通分量求割邊。
我們知道 DFS 一個無向圖將其所有邊分成兩類,樹邊(tree edge)與回邊(back edge)。顯然地,割邊只能是樹邊而絕不可能是回邊。
考慮 一條樹邊 $(u\to v)$ 是割邊 的條件。這條件應當是在DFS樹中,以 $v$ 為根的子樹(簡稱子樹 $v$)中的所有節點都沒有連向 $u$ 的祖先節點(包括 $u$ 本身)的回邊,也就是說子樹 $v$ 僅僅靠着邊 $(u,v)$ 和其他節點保持連通。
為了判斷上述條件,我們在 DFS 過程中記錄每個節點的 dfn 值與 low 值,樹邊 $(u\to v)$ 是割邊的充要條件即是 \(\color{blue}{\mathrm{low}[v]>\mathrm{dfn}[u]}\) 。
struct edge{
int to, nt;
bool flag;
}E[MAX_E<<1];
int head[MAX_V];
int dfn[MAX_V], low[MAX_V];
int ts; //time stamp
void dfs(int u, int f){
dfs[u]=low[u]=++ts;
for(int i=head[i]; ~i; i=E[i].nt){
int &v=E[i].to;
if(!dfn[v]){ //tree edge
dfs(v, f);
low[u]=min(low[u], low[v]);
if(low[v]>dfn[u]){
e[i].flag=e[i^1].flag=true;
}
}
else if(v!=f&&dfn[v]<dfn[u]){ //back edge
low[u]=min(low[u], dfn[v]);
}
}
}
void solve(int N){
memset(dfn, 0, sizeof(dfn));
ts=0;
for(int i=1; i<=N; i++)
if(!dfn[i]) dfs(i, i);
}
現在考慮有重邊的情況。這時上面的寫法不能識別所有回邊。首先明確一點:不論是否有重邊,DFS 都將所有邊分成樹邊與回邊兩類。
但是按上面的寫法,所有重邊要么全是樹邊,要么全是回邊,因而不能識別所有回邊(這並不是 DFS 算法本身有問題,而是寫法有問題)。這是因為 DFS 的參數是 $u$(當前節點)和 $f$(當前節點的父親節點),我們判斷回邊的依據是
else if(v!=f&&dfn[v]<dfn[u]){ //back edge
low[u]=min(low[u], dfn[v]);
}
解決辦法是將參數 $u$換成樹邊 $( u\to f )$ 的編號。
struct edge{
int to, nt, id;
bool tag;
}E[MAX_N<<1];
int head[MAX_N];
int dfn[MAX_N], low[MAX_N], ts; //time_stamp
void dfs(int u, int te){
dfn[u]=low[u]=++ts;
for(int i=head[u]; ~i; i=E[i].nt){
int &v=E[i].to, &id=E[i].id;
if(!dfn[v]){ //tree_edge
dfs(v, id);
low[u]=min(low[u], low[v]);
if(low[v]>dfn[u])
e[i].tag=true;
}
else if(id!=te&&dfn[v]<dfn[u]){ //back_edge
low[u]=min(low[u], dfn[v]);
}
}
}
一般來說,沒必要在結構體 edge 內加個變量 id,按通常的建圖方式,無向邊的 ID 就是其對應的某條有向邊的 ID 右移一位。