tarjan对有向图的缩点(求强连通分量)
tarjan對(duì)有向圖的縮點(diǎn)(求強(qiáng)聯(lián)通分量)
0x00 tarjan算法簡(jiǎn)介
tarjan算法是基于DFS的算法,核心在于巧妙的使用訪問(wèn)節(jié)點(diǎn)的時(shí)間戳 和 棧。
tarjan算法可以用于求解:
- 最近公共祖先(LCA);
- 有向圖的強(qiáng)連通分量;
- 無(wú)向圖的雙連通分量;
- 割點(diǎn);
- 橋;
本篇只介紹用tarjan算法來(lái)求解有向圖的強(qiáng)連通分量。
強(qiáng)連通分量:當(dāng)中任意兩個(gè)節(jié)點(diǎn)互相可達(dá)(如果只有一個(gè)節(jié)點(diǎn),這一個(gè)節(jié)點(diǎn)也是強(qiáng)連通分量)
基本思想:
定義兩個(gè)數(shù)組low[],dfn[],
dfn[i]:記錄訪問(wèn)節(jié)點(diǎn)i時(shí)的次序(時(shí)間戳);
low[i]:節(jié)點(diǎn)i可以追溯到的最小的時(shí)間戳;
dfs訪問(wèn)節(jié)點(diǎn),
訪問(wèn)節(jié)點(diǎn)i時(shí)初始化: low[i] = dfn[i] = 訪問(wèn)編號(hào),同時(shí)讓節(jié)點(diǎn)i入棧,并標(biāo)記i已經(jīng)入棧。
在訪問(wèn)節(jié)點(diǎn)過(guò)程中,會(huì)不斷更新low[]數(shù)組,當(dāng)在回溯過(guò)程中發(fā)現(xiàn)low[i]==dfn[i]時(shí),
說(shuō)明此時(shí)形成了一個(gè)強(qiáng)連通分量,進(jìn)行出棧操作(可進(jìn)行染色標(biāo)記這些節(jié)點(diǎn)屬于強(qiáng)連通),
出棧元素等于此時(shí)的根節(jié)點(diǎn)后,停止出棧(元素出棧時(shí),要記得標(biāo)記已經(jīng)出棧)。繼續(xù)dfs。
下面來(lái)大概模擬一下下方這張圖:
假設(shè)從1號(hào)節(jié)點(diǎn)開(kāi)始dfs訪問(wèn)節(jié)點(diǎn):
low[1] = dfn[1] = 1
stack = {1}
low[2] = dfn[2] = 2
stack = {1,2}
low[4] = dfn[4] = 3
stack = {1,2,4}
此時(shí)在4號(hào)節(jié)點(diǎn),這里假設(shè)之后訪問(wèn)1號(hào)節(jié)點(diǎn),
(當(dāng)然之后也可能先訪問(wèn)5,最后結(jié)果是一樣的)
發(fā)現(xiàn)1號(hào)節(jié)點(diǎn)已經(jīng)在訪問(wèn)過(guò)(在棧中),
那么此時(shí)的4號(hào)節(jié)點(diǎn)的就可以更新,追溯最小的編號(hào):
low[4] = min(low[4],dfn[1]) = 1。
繼續(xù)dfs:
low[5]=dfn[5] = 4
stack = {1,2,4,5}
low[3] = dfn[3] = 5
stack = {1,2,4,5,3}
根節(jié)點(diǎn)在3,沒(méi)有節(jié)點(diǎn)可以訪問(wèn)了,遞歸返回:
發(fā)現(xiàn):low[3] = dfn[3]:
stack中元素出棧(染色進(jìn)行標(biāo)記),出棧元素等于此時(shí)的根節(jié)點(diǎn)后,停止出棧。
此時(shí)出棧的只有:3
{3}是一個(gè)強(qiáng)連通分量;
stack = {1,2,4,5}
繼續(xù) 遞歸返回到 5:
low[5] = min(low[5],low[3]) = min(4,5) = 4
出現(xiàn)了:low[5] = dfn[5]
stack中元素出棧(染色進(jìn)行標(biāo)記),出棧元素等于此時(shí)的根節(jié)點(diǎn)后,停止出棧。
此時(shí)出棧的只有:5
{5}也是一個(gè)強(qiáng)連通分量;
stack = {1,2,4}
繼續(xù) 遞歸返回到 4:
low[4] = min(low[4],low[5]) = min(1,4) = 1
low[4] != dfn[4]
繼續(xù) 遞歸返回到 2:
low[2] = min(low[2],low[4]) = min(2,1) = 1
low[2] != dfn[2]
繼續(xù) 遞歸返回到 1:
low[1] = min(low[1],low[2]) = min(1,1) = 1
出現(xiàn)了:low[1] == dfn[1]
stack中元素出棧(染色進(jìn)行標(biāo)記),出棧元素等于此時(shí)的根節(jié)點(diǎn)后,停止出棧。
{4,2,1}是一個(gè)強(qiáng)連通分量。
stack = {}
最終的強(qiáng)連通分量分別為:{3},{5},{4,2,1}
(…具體看下面代碼,自己手動(dòng)模擬一遍就能夠懂,不好畫(huà)圖,文字表述實(shí)在辛苦。。)
0x01題目
洛谷p2863
有一個(gè) n個(gè)點(diǎn),m 條邊的有向圖,請(qǐng)求出這個(gè)圖點(diǎn)數(shù)大于 1 的強(qiáng)聯(lián)通分量個(gè)數(shù)。
0x02代碼
#include <iostream> #include <algorithm> #include <cstring> using namespace std; const int N = 5e4+5; const int P = 1e4+5; int n,m; struct Edge {int from;int to;int next; }edge[N]; int head[N],id; int low[P]; int dfn[P]; int st[P]; bool vis[P]; int top; int color[P]; int dfn_num; int color_num; int ans; inline void add_edge(int from,int to) {edge[id].from = from;edge[id].to = to;edge[id].next = head[from];head[from] = id++; } void init() {memset(head,-1,sizeof(head));id = 0; } void tarjan(int u) {st[++top] = u;vis[u] = true;dfn[u] = ++dfn_num;low[u] = dfn_num;for(int i = head[u]; ~i; i = edge[i].next){int to = edge[i].to;if(!dfn[to]){tarjan(to);low[u] = min(low[u],low[to]);}else if(vis[to])low[u] = min(low[u],dfn[to]);}if(dfn[u] == low[u]){color[u] = ++color_num;int cnt = 1;vis[u] = false;while(st[top] != u){color[st[top]] = color_num;cnt++;vis[st[top--]] = false;}ans += cnt > 1;top--;} } int main() {cin>>n>>m;init();while(m--){int a,b;cin>>a>>b;add_edge(a,b);}for(int i = 1; i <= n; i++)if(!dfn[i]) tarjan(i);cout<<ans<<endl;return 0; }總結(jié)
以上是生活随笔為你收集整理的tarjan对有向图的缩点(求强连通分量)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 三分法+秦九昭算法
- 下一篇: p话少说,放码过来?