1. 程式人生 > 實用技巧 >Tarjan演算法求割點

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(該集合中最早遍歷到的點的時間戳)
觀察上圖,可以發現割點求法可以分成兩種情況討論。

  1. 若該點為根節點,若有該節點擁有兩個及以上互不相連的子樹,則刪除該點會得到這些子樹。所以該點為割點。
    now == root && child >= 2
  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);
	}
}