強聯通分量及縮點法
概念
1.連通性:如果在圖中存在一條路徑將頂點u,v連線在了一起,則稱u,v是連通的。
2.連通分量:無向圖G的極大連通子圖稱為G的連通分量( Connected Component),就是再加入一個新點,這個新點不能與分量中所有點連通
3.強連通分量:有向圖中, u可達v不一定意味著v可達u. 相互可達則屬於同一個強連通分量(Strongly Connected Component)
4.連通圖:如果圖中所有頂點都是互相連通的,則稱這個圖是一個連通圖
強連通分量及縮點法
我們可以將每個強連通分量看作一個內外隔絕的包裹,忽略包裹內部的冗餘邊,並將這個包裹同外部點的相連的邊保留,將其打包壓縮成一個新的點儲存下來,這就是縮點法。
如圖,s1,s2,s3就是圖的三個強連通分量,可以把他們壓縮成3個新點,壓縮後的新點形成的一定是個有向無環圖,如果新點成環的話就意味著環上的任意兩點相互連通,意味著兩個強連通分量中的點相互連通,則這兩點同屬於一個強連通分量,矛盾
所以縮點法形成的新圖一定是有向無環圖,這個性質有時對解決問題會有極大的幫助。
求連通分量的具體演算法主要有三種,Kosaraju,Gabow和Tarjan演算法,下面對這三種演算法逐一進行介紹。
Tarjan演算法
由於強連通分量中的點相互連通,所以如果用dfs遍歷到這個分量時,一定會回溯到已經遍歷過的同屬於這個分量的點
如圖所示,圖的一個強連通分量會在dfs時會形成以A為根節點的子樹,我們只需要找出這個子樹,並能夠取出這個子樹,也即利用Targan演算法,Targan演算法基於DFS和棧來實現,每次遍歷到一個點時就把該點壓棧。
首先建立兩個陣列DFN[] 和 LOW[], DFN[]用來記錄點被遍歷到的時候的時間,(會再定義一個全域性變數做計時器),作用在於區分點,以及識別根,因為,當DFS走到強連通分量中的第一個點時,這個點的DFN[]一定是最小的,如圖中的A。
LOW[]記錄每個點能夠回溯到的點的最小的DFN值,如B能夠回溯到A,他的LOW實際就是A的DFN
LOW值一定小於DFN
一旦點的DFN不等於其LOW時,意味著他可以回溯到更早的點,所以這個點一定不是根節點。
當一個點的DFN==LOW時,這個點就是根,就將棧中該點及之上的所有點出棧,他們同屬於一個強連通分量。
vector<int> G[10010];
stack<int> s;
int low[10010];
int dfn[10010];
int time = 0;
int scc[10010];
int sccnum = 0;
int visit[10010];
int numinscc[10010];
int outdegree[10010];
void Targan(int u)
{
low[u] = dfn[u] = ++time;
visit[u] = 1;
s.push(u);
for(int i=0;i<G[u].size();i++)
{
int v = G[u][i];
if(visit[v] == 0)
{
Targan(v);
low[u] = min(low[u],low[v]);
}
else if(visit[v] == 1&&scc[v] == 0)
low[u] = min(low[u],dfn[v]);
}
int m;
if(dfn[u] == low[u])
{
sccnum++;
do
{
m = s.top();
s.pop();
scc[m] = sccnum;
numinscc[sccnum]++;
}while(m!=u);
}
}
得到強連通分量之後可以遍歷每條邊,如果邊的兩頂點不在同一個強連通分量,則可以把這個縮點的出度加1
Gabow演算法
Gabow演算法的原理和Targan演算法類似,只是Gabow演算法將LOW陣列用另一個棧代替,即用雙棧實現演算法
每次遍歷到新點時,就把該點同時壓入兩個棧,因為強連通分量是由一個個環組成的,所以每當回溯到棧中的點導致成環時
就把棧2中該環內根節點以上的點彈出,只保留根節點,當從某點出發全部dfs完了之後,棧二的頂點就是該點,那麼這個點就是強連通分量的根節點,這時棧1該點及以上的所有點就組成了強連通分量,即慢慢剝離強連通分量中的環達到定位根節點的目的
Gabow演算法也利用了陣列DFN來為節點編序。
vector<int> G[10010];
stack<int> s1;
stack<int> s2;
//int low[10010];
int dfn[10010];
int time = 0;
int scc[10010];
int sccnum = 0;
int visit[10010];
int outdegree[10010];
int numinscc[10010];
void Gabow(int u)
{
visit[u] =1;
dfn[u] = ++time;
s1.push(u);
s2.push(u);
for(int i=0;i<G[u].size();i++)
{
int v = G[u][i];
if(visit[v] == 0)
Gabow(v);
else if(visit[v]==1&&scc[v]==0)
{
while(dfn[s2.top()]>dfn[v])
s2.pop();
}
}
if(s2.top() == u)
{
int m;
sccnum++;
do
{
m=s1.top();
s1.pop();
scc[m] = sccnum;
numinscc[sccnum]++;
}while(m!=u);
}
}
Kosaraju演算法
但是會發現,如果我們先遍歷B中的頂點,則第一次DFS將遍歷B3、B4、B5組成的強連通分量。第二次DFS將遍歷A0、A1、A2
組成的強連通分量,這樣我們就想到一個策略就是如果能得到一個頂點遍歷的順序,滿足每次按順序遍歷一次DFS,就能遍歷出一個強連通分量就好了,好的是這樣的順序是存在的
我們把原圖反向,所有的邊反向
建立一個棧,在DFS,當頂點所有的邊都被遍歷完時,把這個頂點壓入棧中
第一種情況,先遍歷A0、A1、A2,則第一次DFS後,三點全部入棧,第二次DFS後B3、B4、B5入棧,滿足B系列的點在A系列的點上面(在棧中)
第二種情況,先遍歷B系列的點,因為壓棧操作在所有的邊被遍歷完之後,所以當B系列的點要被壓棧時,A系列的點已經遍歷完了,所以B系列的點依然在A系列的點上面。這樣從棧頂到棧頂的頂點形成的順序就是我們要的序列。
按這個順序DFS就得到了各強連通分量,這個方法對複雜情況也是成立的。即演算法分為兩步:
(1)對原圖取反,從任意一個頂點開始對反向圖進行逆後續DFS遍歷
(2)按照逆後續遍歷中棧中的頂點出棧順序,對原圖進行DFS遍歷,一次DFS遍歷中訪問的所有頂點都屬於同一強連通分量。
vector<int>G[maxn],G2[maxn];
vector<int>S;
int vis[maxn],sccno[maxn],scc_cnt;
void dfs1(int u)
{
if (vis[u]) return;
vis[u]=1;
for (int i=0;i<G[u].size();i++) dfs1(G[u][i]);
S.push_back(u);
}
void dfs2(int u)
{
if (sccno[u]) return;
sccno[u]=scc_cnt;
for (int i=0;i<G2[u].size();i++) dfs2(G2[u][i]);
}
void find_scc(int n)
{
scc_cnt=0;
S.clear();
memset(sccno,0,sizeof(sccno));
memset(vis,0,sizeof(vis));
for (int i=0;i<n;i++) dfs1(i);
for (int i=n-1;i>=0;i--)
{
if (!sccno[S[i]])
{
scc_cnt++;
dfs2(S[i]);
}
}
}
例題:POJ2186
考慮這樣一個例子,先求出圖中的強連通分量,然後縮點成新圖
則S3中的牛都是滿足題意的受所有牛仰慕的牛,即縮點後的新圖若只有一個出度為零的點,則這個點就是滿足題意的點
該點內的所有牛都是滿足題意的牛,若不止一個出度為0的點,則滿足提議的牛為0
下面僅附上Targan演算法程式
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <vector>
#include <stack>
using namespace std;
vector<int> G[10010];
stack<int> s;
int low[10010];
int dfn[10010];
int time = 0;
int scc[10010];
int sccnum = 0;
int visit[10010];
int numinscc[10010];
int outdegree[10010];
void Targan(int u)
{
low[u] = dfn[u] = ++time;
visit[u] = 1;
s.push(u);
for(int i=0;i<G[u].size();i++)
{
int v = G[u][i];
if(visit[v] == 0)
{
Targan(v);
low[u] = min(low[u],low[v]);
}
else if(visit[v] == 1&&scc[v] == 0)
low[u] = min(low[u],dfn[v]);
}
int m;
if(dfn[u] == low[u])
{
sccnum++;
do
{
m = s.top();
s.pop();
scc[m] = sccnum;
numinscc[sccnum]++;
//outdegree[sccnum] += G[m].size();
}while(m!=u);
}
}
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int u,v;
cin>>u>>v;
G[u].push_back(v);
}
for(int i=1;i<=n;i++)
{
if(scc[i]==0)
Targan(i);
}
for(int i =1;i<=n;i++)
{
for(int j=0;j<G[i].size();j++)
{
int v = G[i][j];
if(scc[i]!=scc[v])
outdegree[scc[i]]++;
}
}
int nq = 0,num = 0;
for(int i=1;i<=sccnum;i++)
{
if(outdegree[i]==0)
{num++;nq = i;}
}
if(num==1)
cout << numinscc[nq]<<endl;
else
cout << 0 <<endl;
}
相關文章
- 強連通分量及縮點 演算法解析及例題演算法
- 【模板】tarjan 強連通分量縮點
- 強聯通分量tarjan
- 強連通------tarjan演算法詳解及與縮點聯合運用演算法
- 強連通分量
- UVA1327 && POJ1904 King's Quest(tarjan+巧妙建圖+強連通分量+縮點)
- 無向連通圖點雙連通分量
- Day7 割點、割邊和強連通分量
- Tarjan求強連通分量
- 圖論系列之「深度優先遍歷及聯通分量」圖論
- 【演算法學習】tarjan 強連通、點雙、邊雙及其縮點 重磅來襲!!!!演算法
- 強連通分量(Tarjan演算法)演算法
- POJ 1236 Network of Schools 強連通分量
- 有向圖的強連通分量 模版
- Tarjan演算法(強連通分量分解)演算法
- Tarjan 求有向圖的強連通分量
- 圖論——強連通分量(Tarjan演算法)圖論演算法
- 【Tarjan SCC 加邊使得所有圖聯通 至少選取多少個點能圖聯通 】Network of Schools加強版.md
- 無向連通圖邊雙連通分量
- kosaraju 和 tarjan演算法詳解(強連通分量)演算法
- UVA-11504 - Dominos(有向圖的強連通分量)
- 縮點
- WebRTC 及點對點網路通訊機制Web
- 詳解中括號語法及點語法
- 直流分量2
- 「學習筆記」雙連通分量、割點與橋筆記
- SCC縮點
- 利聯科技:盤點FTP伺服器無法登陸的原因及解決方案FTP伺服器
- 邊分治維護強連通分量(CF1989F,P5163)
- 尋找圖的強連通分量:tarjan演算法簡單理解演算法
- JS壓縮方法及批量壓縮JS
- 樂訊通雲通訊:物聯網路卡有什麼優點
- Counterpoint:爭加劇 聯發科與高通的差距逐漸縮小
- web前端頁面點選預覽圖片及大小縮放Web前端
- Zotero Translate 聯動 DeepL翻譯 強強聯手
- 20行程式碼實現,使用Tarjan演算法求解強連通分量行程演算法
- 常用CSS縮寫語法CSS
- 樂訊通雲通訊:什麼是物聯網路卡?物聯網路卡的優點是什麼?