Tarjan演算法求割點
(宣告:以下圖片來源於網路)
Tarjan演算法求出割點個數
首先來了解什麼是連通圖
在圖論中,連通圖基於連通的概念。在一個無向圖 G 中,若從頂點i到頂點j有路徑相連(當然從j到i也一定有路徑),則稱i和j是連通的。如果 G 是有向圖,那麼連線i和j的路徑中所有的邊都必須同向。如果圖中任意兩點都是連通的,那麼圖被稱作連通圖。如果此圖是有向圖,則稱為強連通圖(注意:需要雙向都有路徑)。圖的連通性是圖的基本性質。
——摘自度娘
通俗易懂,不在解釋。
舉個例子吧:
如上圖,各個節點皆可以到達任意節點,所以該圖為連通圖。
再來了解什麼是割點
在一個無向圖中,如果有一個頂點集合,刪除這個頂點集合以及這個集合中所有頂點相關聯的邊以後,圖的連通分量增多,就稱這個點集為割點集合。
如果某個割點集合只含有一個頂點X(也即{X}是一個割點集合),那麼X稱為一個割點。
——摘自度娘
瞭解完定義之後,不難通過定義來求出圖的割點——暴力DFS。
即是:從1到n遍歷每一個點,每次遍歷到這個點時,只需要刪除該點,判斷刪除後是否會增加聯通量即可。
這種方法時間複雜度最壞為\(O(n×(n+m))\),只要資料大一點就會被卡爆,這裡不詳細敘述。
使用Tarjan演算法求割點
可以參考本人的Tarjan演算法縮點部落格
依然定義:dfn(時間戳),low(該集合中最早遍歷到的點的時間戳)
觀察上圖,可以發現割點求法可以分成兩種情況討論。
- 若該點為根節點,若有該節點擁有兩個及以上互不相連的子樹,則刪除該點會得到這些子樹。所以該點為割點。
(now == root && child >= 2 - 若該點不為根節點,若不存在可以在DFS中可以遇到已訪問節點的連邊時(通俗的說,就是不可以找到回去的路),則該點為割點。
(now != root && low[next] >= dfn[now])
low的值可以證明發現:
low的初始值為該節點的時間戳。
即是:low[now]=dfn[now]。
若當前結點 now 的所連結點 next 正在被訪問,則 low[now]=min(low[now],dfn[next]) 。
若當前結點 now 的所連結點 next 未被訪問,則
low[now]=min(low[now],low[next])。
C++程式碼實現:
void Tarjan(int now, int father, int root) {
dfn[now] = low[now] = ++tim;
int child = 0; //若now為根節點時子樹的個數
int SIZ = v[now].size();
for(int i = 0; i < SIZ; i++) {
int next = v[now][i];
if(!dfn[next]) {
if(now == root) {
child++;
}
Tarjan(next, now, root);
low[now] = min(low[now], low[next]);
if(now == root && child >= 2)//處理情況一
cut[now] = true;
else if(now != root && low[next] >= dfn[now])//處理情況二
cut[now] = true;
}
if(next != father)
low[now] = min(low[now], dfn[next]);//更新low值
}
}
void Build() {
for(int i = 1; i <= n; i++)
if(!dfn[i])
Tarjan(i, i, i);
}
題目背景
割點
題目描述
給出一個\(n\)個點,\(m\)條邊的無向圖,求圖的割點。
輸入格式
第一行輸入兩個正整數\(n,m\)。
下面\(m\)行每行輸入兩個正整數\(x,y\)表示\(x\)到\(y\)有一條邊。
輸出格式
第一行輸出割點個數。
第二行按照節點編號從小到大輸出節點,用空格隔開。
輸入輸出樣例
輸入
6 7
1 2
1 3
1 4
2 5
3 5
4 5
5 6
輸出
1
5
思路
板題,套上即可。
C++程式碼:
#include <cstdio>
#include <vector>
using namespace std;
const int MAXN = 2e4 + 5;
vector<int> v[MAXN];
int dfn[MAXN], low[MAXN];
bool cut[MAXN];
int tim, cnt;
int n, m;
void Read();
void Build();
void Tarjan(int, int, int);
int main() {
Read();
Build();
return 0;
}
void Build() {
for(int i = 1; i <= n; i++)
if(!dfn[i])
Tarjan(i, i, i);
for(int i = 1; i <= n; i++)
if(cut[i])
cnt++;
printf("%d\n", cnt);
for(int i = 1; i <= n; i++)
if(cut[i])
printf("%d ", i);
}
void Tarjan(int now, int father, int root) {
dfn[now] = low[now] = ++tim;
int child = 0;
int SIZ = v[now].size();
for(int i = 0; i < SIZ; i++) {
int next = v[now][i];
if(!dfn[next]) {
if(now == root) {
child++;
}
Tarjan(next, now, root);
low[now] = min(low[now], low[next]);
if(now == root && child >= 2)
cut[now] = true;
else if(now != root && low[next] >= dfn[now])
cut[now] = true;
}
if(next != father)
low[now] = min(low[now], dfn[next]);
}
}
void Read() {
scanf("%d %d", &n, &m);
for(int i = 1; i <= m; i++) {
int A, B;
scanf("%d %d", &A, &B);
v[A].push_back(B);
v[B].push_back(A);
}
}