圖論——強連通分量(Tarjan演算法)
強連通分量
什麼是強連通圖?
如果一個有向圖中,存在一條迴路,所有的結點至少被經過一次,這樣的圖為強連通圖。
什麼是強連通分量?
在強連圖圖的基礎上加入一些點和路徑,使得當前的圖不在強連通,稱原來的強連通的部分為強連通分量。
求強連通分量有何作用?
在進行對其它圖論問題的求解前,利用強連通分量的知識可以把圖中強連通的點縮為一個點,減少接下來其它圖論操作的計算。
在某些特定的環境下,求強連通分量變相地得出圖中的環以及環的長度。
利用Tarjan演算法求強連通分量
Tarjan演算法的基本思路
首先考慮強連通分量的性質,即存在一條迴路能從初始點又回到初始點。在這個查詢的過程中,可以對經過的結點標記,當發現某一節點連向的點正好以及被標記過,則說明找到了一條迴路,而這個迴路上的所有點構成一個強連通分量。為了儲存這個強連通分量,我們需要知道這條路上有哪些點,而此時,棧就是一種適合該演算法的資料結構。對於每次搜尋的點,我們都加入棧中,遇到迴路時,在把棧中的元素逐個彈出,記錄它們的起始結點,直到棧中彈出的元素正好是起始結點時,結束彈棧,繼續搜尋其它強連通分量。在這個過程中,所有的點和都有的邊都被遍歷了一次,所以最終的時間複雜度為
O
(
N
+
E
)
O(N+E)
O(N+E)
Tarjan演算法的實現
為了實現這個過程,Tarjan演算法需要裝備如下幾樣東西:
記錄搜尋順序的陣列
d
f
n
dfn
dfn;
記錄所屬強連通的陣列
l
o
w
low
low;
表示某結點是否在棧中的陣列
i
n
s
t
a
c
k
instack
instack;
一個棧儲存搜尋路徑;
從上面對Tarjan演算法的描述中,很容易看出這個演算法是基於深度優先搜尋實現的。接下來,對Tarjan演算法進行逐步推演。
(本人不喜歡使用網上用爛的素材,接下來包括以前的講解圖都是自己手畫的,不喜勿噴)
Tarjan演算法虛擬碼
tarjan(u)
{
dfn[u]=low[u]=++Index
stack.push(u)
for each (u, v) in E
if (v is not visted)
tarjan(v)
low[u] = min(low[u], low[v])
else if (v in S)
low[u] = min(low[u], dfn[v])
if (dfn[u] == low[u])
repeat
v = stack.pop
print v
until (u== v)
}
Tarjan演算法C++程式碼模板
#include<iostream>
#include<stack>
#include<vector>
using namespace std;
int n,m,cnt,cntb;
vector<int> edge[101];
vector<int> belong[101];
bool instack[101];
int dfn[101];
int low[101];
stack<int> s;
void Tarjan(int u)
{
++cnt;
dfn[u]=low[u]=cnt;
s.push(u);
instack[u]=true;
for(int i=0;i<edge[u].size();++i)
{
int v=edge[u][i];
if(!dfn[v])
{
Tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(instack[v])
low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u])
{
++cntb;
int node;
do
{
node=s.top();
s.pop();
instack[node]=false;
belong[cntb].push_back(node);
}while(node!=u);
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;++i)
{
int u,v;
cin>>u>>v;
edge[u].push_back(v);
}
Tarjan(1);
cout<<"id :";
for(int i=1;i<=n;++i)
cout<<i<<" ";
cout<<endl;
cout<<"dfn :";
for(int i=1;i<=n;++i)
cout<<dfn[i]<<" ";
cout<<endl;
cout<<"low :";
for(int i=1;i<+n;++i)
cout<<low[i]<<" ";
cout<<endl;
for(int i=1;i<=cntb;++i)
{
cout<<"SCG "<<i<<" : ";
for(int j=0;j<belong[i].size();++j)
cout<<belong[i][j]<<" ";
cout<<endl;
}
return 0;
}
按照先前的推演圖,生成測試樣例
7 11
1 2
2 3
2 5
2 4
3 5
3 7
7 5
5 6
6 7
4 1
4 5
執行結果與推演時一致
來一道例題練手(USACO08DEC)
——題目描述——
每年,在威斯康星州,奶牛們都會穿上衣服,收集農夫約翰在N(1<=N<=100,000)個牛棚隔間中留下的糖果,以此來慶祝美國秋天的萬聖節。
由於牛棚不太大,FJ通過指定奶牛必須遵循的穿越路線來確保奶牛的樂趣。為了實現這個讓奶牛在牛棚裡來回穿梭的方案,FJ在第i號隔間上張貼了一個“下一個隔間”Next_i(1<=Next_i<=N),告訴奶牛要去的下一個隔間;這樣,為了收集它們的糖果,奶牛就會在牛棚裡來回穿梭了。
FJ命令奶牛i應該從i號隔間開始收集糖果。如果一隻奶牛回到某一個她已經去過的隔間,她就會停止收集糖果。
在被迫停止收集糖果之前,計算一下每頭奶牛要前往的隔間數(包含起點)。
——輸入格式——
第1行 整數n。
第2行到n+1行 每行包含一個整數 next_i 。
——輸出格式——
n行,第i行包含一個整數,表示第i只奶牛要前往的隔間數。
——輸入樣例——
4
1
3
2
3
——輸出樣例——
1
2
2
3
——題解——
本題的資料量還是蠻大的,有
1
e
5
1e5
1e5個結點,我們當然不可能對每一個結點進行深度優先搜尋。幸運的是,每一個點都只有一條出邊,也即意味著,本題中至少有一個環(包括自環),而且,環上的點永遠無法走出這個環,環上點的答案就是這個環的長度。而對於不在環上的點,用dfs直到找到一個環,其答案也即環的長度加上搜尋到環的步數。環長度的計算,可以利用tarjan演算法的思路,用dfn減去low。因地制宜,我寫了一個簡易版的tarjan.
——Code——
#include<iostream>
#include<stack>
#include<algorithm>
using namespace std;
int edge[100005];
int dfn[100005];
bool vis[100005];
int ans[100005];
stack<int> sta;
int n;
int cnt_dfn;
void dfs(int u)
{
++cnt_dfn;
dfn[u]=cnt_dfn;
vis[u]=true;
sta.push(u);
if(vis[edge[u]])
{
if(!ans[edge[u]])
{
int val=dfn[u]-dfn[edge[u]]+1;
int v;
do
{
v=sta.top();
ans[v]=val;
sta.pop();
}while(v!=edge[u]);
while(!sta.empty())
{
++val;
ans[sta.top()]=val;
sta.pop();
}
}
else
{
int val=ans[edge[u]];
while(!sta.empty())
{
++val;
ans[sta.top()]=val;
sta.pop();
}
}
}
else dfs(edge[u]);
}
int main()
{
cin>>n;
for(int i=1;i<=n;++i)
{
cin>>edge[i];
if(edge[i]==i)
{
ans[i]=1;
vis[i]=true;
}
}
for(int i=1;i<=n;++i)
if(!vis[i])
{
cnt_dfn=0;
dfs(i);
}
for(int i=1;i<=n;++i)
cout<<ans[i]<<endl;
return 0;
}
相關文章
- 圖之強連通、強連通圖、強連通分量 Tarjan演算法演算法
- 強連通分量(Tarjan演算法)演算法
- 圖論複習之強連通分量以及縮點—Tarjan演算法圖論演算法
- Tarjan演算法(強連通分量分解)演算法
- Tarjan 求有向圖的強連通分量
- Tarjan演算法求強連通分量總結演算法
- 強連通分量-tarjan演算法模板詳解演算法
- 尋找圖的強連通分量:tarjan演算法簡單理解演算法
- kosaraju 和 tarjan演算法詳解(強連通分量)演算法
- Tarjan演算法三大應用之強連通分量演算法
- 強連通分量及縮點tarjan演算法解析演算法
- 【模板】tarjan 強連通分量縮點
- POJ 2186 Popular Cows(強連通分量縮點,Tarjan演算法)演算法
- 連通圖與Tarjan演算法演算法
- 20行程式碼實現,使用Tarjan演算法求解強連通分量行程演算法
- 強連通演算法--Tarjan個人理解+詳解演算法
- 強連通分量與縮點(Tarjan演算法)(洛谷P3387)演算法
- 強連通分量
- 連通圖演算法詳解之① :Tarjan 和 Kosaraju 演算法演算法
- 強連通圖的演算法演算法
- HDU 2767 Proving Equivalences Tarjan 強連通縮點UI
- 強連通------tarjan演算法詳解及與縮點聯合運用演算法
- 演算法學習之路|強連通分量+縮點演算法
- 【筆記】tarjian演算法 求強連通分量筆記演算法
- 【演算法學習】tarjan 強連通、點雙、邊雙及其縮點 重磅來襲!!!!演算法
- 強連通分量及縮點 演算法解析及例題演算法
- 求有向圖的強連通分量(c語言版)C語言
- POJ 3592 Instantaneous Transference 圖論演算法tarjan+spfa圖論演算法
- 有向圖的連通性(判強連通)
- Trajan演算法(強連通+縮點)演算法
- HDU2767Proving Equivalences[強連通分量 縮點]UI
- 【Tarjan SCC 加邊使得所有圖聯通 至少選取多少個點能圖聯通 】Network of Schools加強版.md
- 圖論系列之「深度優先遍歷及聯通分量」圖論
- 圖論連通性圖論
- Tarjan演算法_縮點演算法
- 抓間諜(強連通)
- 邊分治維護強連通分量(CF1989F,P5163)
- 圖論演算法圖論演算法