連通圖小結 A Summary for Connected Graph
Ⅰ.概念
- 強連通
- 強連通:有向圖中
(u,v) 存在u→v, v→u 的兩條路徑,稱(u,v) 爲強連通 - 強連通圖:有向圖中任意兩個頂點強連通
- 強連通分量:有向圖的極大強連通子圖
- 強連通:有向圖中
- 弱連通
- 弱連通:無向圖中
(u,v) 存在u−v 的路徑,稱(u,v) 爲弱連通 - 弱連通圖:無向圖中任意兩個頂點弱連通,(可以將忽略方向的有向圖看作無向圖)
- [弱]連通分量:無向圖的極大[弱]連通子圖
- 弱連通:無向圖中
- 割點與橋
- 割點:無向圖中點
u ,刪去後連通分量數目增加(連通圖則使得圖不再連通),稱u 爲割點 - 橋:無向圖中邊
(u,v) ,刪去後連通分量數目增加(連通圖則使得圖不再連通),稱(u,v) 爲橋(割邊)
- 割點:無向圖中點
- 點雙連通
- 點雙連通:無向圖中
(u,v) 存在至少兩條“點不重複”的路徑,稱(u,v) 爲點雙連通
這個要求等價於任意兩條邊都在同一個簡單環中,即內部無割點 - 點雙連通分量:無向圖的極大點雙連通子圖。
- 性質:
- 無向圖每條邊恰好屬於一個點雙連通分量
- 不同的點雙連通分量之間可能會有公共點,最多隻有一個且一定是割點
- 任意割點都是至少兩個不同點雙連通分量的公共點
- 點雙連通:無向圖中
- 邊雙連通
- 邊雙連通:無向圖中
(u,v) 存在至少兩條“邊不重複”的路徑,稱(u,v) 爲邊雙連通
這個要求低一點,只需要每條邊都至少在一個簡單環中,即所有的邊都不是橋 - 邊雙連通分量:無向圖的極大邊雙連通子圖。
- 性質:
- 除橋不屬於任何邊雙連通分量外,其他每條邊恰好屬於一個邊雙連通分量
- 把所有橋刪除後,每個連通分量對應原圖的一個邊雙連通分量
- 邊雙連通:無向圖中
Ⅱ.連通圖算法
- 連通分量
Connected Component
求解連通分量只需要dfs 或者bfs 把圖搜一遍就好了,當然並查集也是可以的。
一般不爆棧的情況,dfs 好寫適用性強
//Connected Components
vector<int> G[N];
int id[N], cc;
void dfs(int u) {
id[u] = cc;
for(int v : G[u]) {
if(id[v]) continue;
dfs(v);
}
}
void findCC() {
cc = 0;
for(int i = 1; i <= n; ++i) {
if(id[i]) continue;
++cc;
dfs(i);
}
}
- 割點
Cut
基於dfs 時間戳的Tarjan 算法
dfn[u]:= u 的時間戳,low[u]:= u 及其後代能連回的最早的祖先的時間戳
如果low[v]≥dfn[u] ,即v 及其所在的子樹都沒有後向邊(back edge) 連回u的祖先
如果刪去u ,那麼u 的祖先與v 及其所在的子樹不連通,根據“連通”關係的傳遞性,整個圖就不連通了
//Tarjan-cut
vector<int> G[N];
int dfn[N], low[N], cut[N], dfsNum;
void tarjan(int u, int f) {
dfn[u] = low[u] = ++dfsNum;
int son = 0;
for(int v : G[u]) {
if(v == f) continue;
if(!dfn[v]) {
++son;
tarjan(v, u);
low[u] = min(low[u], low[v]);
if(low[v] >= dfn[u]) cut[u] = true;
} else low[u] = min(low[u], dfn[v]);
}
if(f < 0 && son == 1) cut[u] = false;
}
void init() {
dfsNum = 0;
memset(dfn, 0, sizeof dfn);
memset(cut, false, sizeof cut);
tarjan(root, -1);
}
- 橋
Bridge
基於dfs 時間戳的Tarjan 算法
dfn[u]:= u 的時間戳,low[u]:= u 及其後代能連回的最早的祖先的時間戳
如果low[v]>dfn[u] ,即v 及其所在的子樹只能連回v 自己
如果刪去(u,v) ,那麼u 與v 及其所在的子樹不連通,根據“連通”關係的傳遞性,整個圖就不連通了
//Tarjan-bridge
vector<int> G[N];
vector<pair<int, int> > bridge;
int dfn[N], low[N], dfsNum;
void tarjan(int u, int f) {
dfn[u] = low[u] = ++dfsNum;
for(int v : G[u]) {
if(v == f) continue;
if(!dfn[v]) {
tarjan(v, u);
low[u] = min(low[u], low[v]);
if(low[v] > dfn[u]) bridge.push_back({u, v});
} else low[u] = min(low[u], dfn[v]);
}
}
void init() {
dfsNum = 0;
bridge.clear();
memset(dfn, 0, sizeof dfn);
tarjan(root, -1);
}
- 點雙連通分量
Biconnected Component or Block
基於dfs 時間戳的Tarjan 算法
dfn[u]:= u 的時間戳,low[u]:= u 及其後代能連回的最早的祖先的時間戳
vector<int> G[N];
int dfn[N], low[N], cut[N], bcc, dfsNum;
vector<int> block[N];
int stk[N], top;
void tarjan(int u, int f) {
dfn[u] = low[u] = ++dfsNum;
stk[++top] = u;
int son = 0;
for(int v : G[u]) {
if(v == f) continue;
if(!dfn[v]) {
++son;
tarjan(v, u);
low[u] = min(low[u], low[v]);
if(low[v] >= dfn[u]) {
cut[u] = true;
block[++bcc].push_back(u);
while(true) {
int x = stk[top--];
block[bcc].push_back(x);
if(x == v) break;
}
}
} else low[u] = min(low[u], dfn[v]);
}
if(f < 0 && son == 1) cut[u] = false;
}
void init() {
bcc = dfsNum = 0;
memset(dfn, 0, sizeof dfn);
memset(cut, 0, sizeof cut);
tarjan(root, -1);
}
- 邊雙連通分量
Edge−biconnected Component
基於dfs 時間戳的Tarjan 算法
dfn[u]:= u 的時間戳,low[u]:= u 及其後代能連回的最早的祖先的時間戳
邊雙連通縮點其實就是留下橋,把所有橋邊構成了新圖,新圖是一棵樹
//Tarjan-bcc
vector<int> G[N];
int dfn[N], low[N], in[N], id[N], bcc, dfsNum;
int stk[N], top;
void tarjan(int u, int f) {
dfn[u] = low[u] = ++dfsNum;
stk[++top] = u;
in[u] = true;
for(int v : G[u]) {
if(v == f) continue;
if(!dfn[v]) {
tarjan(v, u);
low[u] = min(low[u], low[v]);
} else low[u] = min(low[u], dfn[v]);
}
if(low[u] == dfn[u]) {
++bcc;
while(true) {
int v = stk[top--];
in[v] = false;
id[v] = bcc;
if(v == u) break;
}
}
}
void init() {
bcc = dfsNum = 0;
memset(dfn, 0, sizeof dfn);
tarjan(root, -1);
}