強連通演算法--Tarjan個人理解+詳解
tarjan用來求強連通分量的!說白了就是 A到B,B到了到不了A。
運用了dfs,堆疊的知識
討論如何用Tarjan演算法求強連通分量個數:
假設我們要先從0號節點開始Dfs,我們發現一次Dfs我萌就能遍歷整個圖(樹),而且我們發現,在Dfs的過程中,我們深搜到 了其他強連通分量中,那麼俺們Dfs之後如何判斷他喵的哪個和那些節點屬於一個強連通分量呢?我們首先引入兩個陣列:
①dfn【】DFN[]時間戳,簡單來說就是 第幾個被搜尋到的,每個點的時間戳都不一樣
②low【】LOW[]作為每個點在這顆樹中的,最小的子樹的根,每次保證最小,like它的父親結點的時間戳這種感覺。
如果它自己的LOW[]最小,那這個點就應該從新分配,變成這個強連通分量子樹的根節點。
舉例
我們定義low【u】=min(low【u】,low【v】(即使v搜過了也要進行這步操作,但是v一定要在棧內才行)),u代表當前節點,v代表其能到達的節點。
這個陣列在剛剛到達節點u的時候初始化:low【u】=dfn【u】。然後在進行下一層深搜之後回溯回來的時候,維護low【u】。
如果我們發現了某個節點回溯之後的low【u】值還是==dfn【u】的值,那麼這個節點無疑就是一個關鍵節點
從這個節點能夠到達其強連通分量中的其他節點,但是沒有其他屬於這個強連通分量以外的點能夠到達這個點
所以這個點的low【u】值維護完了之後還是和dfn【u】的值一樣,口述可能理解還是相對費勁一些,我們走一遍流程圖
①首先進入0號節點,初始化其low【0】=dfn【0】=1,然後深搜到節點2,初始化其:low【2】=dfn【2】=2,然後深搜到節點1,初始化其:low【1】=dfn【1】=3;
②然後從節點1開始繼續深搜,發現0號節點已經搜過了,沒有繼續能夠搜的點了,開始回溯維護其值。low【1】=min(low【1】,low【0】)=1;low【2】=min(low【2】,low【1】)=1;low【0】=min(low【0】,low【2】)=1;
③這個時候猛然發現,low【0】==dfn【0】,這個時候不要太開心,就斷定一定0號節點是一個關鍵點,別忘了,這個時候還有3號節點沒有遍歷,我們只有在其能夠到達的節點全部判斷完之後,才能夠下結論,所以我們繼續Dfs。
④繼續深搜到3號節點,初始化其low【3】=dfn【3】=4,然後深搜到4號節點,初始化其:low【4】=dfn【4】=5,這個時候發現深搜到底,回溯,因為節點4沒有能夠到達的點,所以low【4】也就沒有幸進行維護即:low【4】=dfn【4】(這個點一定是強連通分量的關鍵點,但是我們先忽略這個點,這個點沒有代表性,一會分析關鍵點的問題),然後回溯到3號節點,low【3】=min(low【3】,low【4】)=4;發現low【3】==dfn【3】那麼這個點也是個關鍵點,我們同樣忽略掉。
⑤最終回溯到節點0,進行最後一次值的維護:low【0】=min(low【0】,low【3】)=0,這個時候我們猛然發現其dfn【0】==low【0】,根據剛才所述,那麼這個點就是一個關鍵點:能夠遍歷其屬強連通分量的點的起始點,而且沒有其他點屬於其他強連通分量能夠有一條有向路徑連到這個節點來的節點。
因為這個點屬於一個強連通分量,而且強連通分量中的任意兩個節點都是互達的,也就是說強連通分量中一定存在環,這個最後能夠回到0號節點的1號節點一定有機會維護low【1】,因為0號節點是先進來的,所以其low【1】的值也一定會跟著變小,然後在回溯的過程中,其屬一個強連通分量的所有點都會將low【u】值維護成low【0】,所以這個0號節點就是這個關鍵點:能夠遍歷其屬強連通分量的起始點而且這樣的起始點一定只有一個,所以只要發現了一個這樣的關鍵起始點,那麼就一定發現了一個強連通分量。而且這個節點沒有其他點屬於其他強連通分量能夠有一條有向路徑連到這個節點來的節點:如果這樣的點存在,那麼這些個點應該屬於同一個強連通分量。
那麼綜上所述,相信大家也就能夠理解為什麼dfn【u】==low【u】的時候,我們就可以判斷我們發現了一個強連通分量了。
#include <iostream>
#include <algorithm>
#include<string>
#include<bits/stdc++.h>
#define maxn 1000
//此程式碼僅供參考,用於求一個圖存在多少個強連通分量
using namespace std;
vector<int>mp[maxn];
int vis[maxn];
int dfn[maxn];
int low[maxn];
int n,m,sig=0,cnt=0;
void init()
{
memset(low,0,sizeof(low));
memset(dfn,0,sizeof(dfn));
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++)
mp[i].clear();
}
void tarjin(int u)
{
vis[u]=1;
low[u]=dfn[u]=cnt++;
for(int i=0;i<mp[u].size();i++)
{
int v=mp[u][i];
if(vis[v]==0)tarjin(v);//遞迴
if(vis[v]==1)low[u]=min(low[u],low[v]); //晦朔
}
//判點
if(dfn[u]==low[u])
sig++;
}
void slove()//詢問有多少個強連通分量
{
sig=0;
for(int i=1;i<=n;i++)//跑一遍點集
{
if(vis[i]==0)//串一下各個聯通集
tarjin(i);//跑dfs
}
cout<<sig<<endl;
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
if(n==0)break;//點的個數
scanf("%d",&m);
init();
for(int i=0;i<m;i++)//存圖方式
{
int x,y;
scanf("%d%d",&x,&y);
mp[x].push_back(y);
}
slove();
}
return 0;
}