Tarjan求強連通分量
[演算法定義]
在有向圖中,如果兩個頂點至少存在一條路徑(可以相互通達),則稱兩個頂點強連通(strongly connected)。
如果有向圖G的每兩個頂點都強連通,稱G是一個強連通圖。
非強連通有向圖的極大強連通子圖,稱為強連通分量(strongly connected components)。
在上圖中,{1 , 2 , 3 , 4 } , { 5 } , { 6 } 三個區域可以相互連通,稱為這個圖的強連通分量。
Tarjan演算法是基於對圖深度優先搜尋的演算法,每個強連通分量為搜尋樹中的一棵子樹。
DFN[ i ] : 在DFS中該節點i被搜尋的次序(時間戳)。
LOW[ i ] : 為i或i的子樹能夠追溯到的最早的棧中節點的次序號。
當DFN[ i ]==LOW[ i ]時,已i為根的搜尋子樹上所有節點是一個強連通分量。
【心得】:如果有環,dfn是傳遞自己的下一代,low是繼承自己的上一代或自己(上一代無環)
搜尋時,把當前搜尋樹中未處理的節點加入一個堆疊。
回溯時可以判斷棧頂到棧中的節點是否為一個強連通分量。
[演算法圖解]
從1開始dfs搜尋,把遍歷到的節點加入棧中。
搜尋到i=6時,節點都入棧了,此時就進行回溯。
DFN[6]=LOW[6],以節點6為根的搜尋子樹是一個強連通分量(節點6沒有子樹)。
6出棧。
依次類推,DFN[5]=LOW[5],5為強連通分量。
並且5的邊也都找完了,5出棧。
接下來回溯到3,4入棧。
然後從4找到1,發現節點1已存在。
將1看做根節點往回搜尋子節點,子節點LOW[i]=low[根]=1。
子節點low繼承的是根的dfn,根的low就是根的dfn,最小的那個
現在只是將1,3,4看做環,1的邊還沒有找完。
沒有找完自然不會進行根節點1成環的回溯出棧操作。
繼續找1的邊,找到2。
再訪問4(還在棧中),所以LOW[2]=DFN[4]=5。
那麼4的根是2,為什麼不繼承1的dfn,是為了讓縮點與割點程式碼一致
結果不影響,連通分量還是一起出棧(並染色)
從2返回1後,發現DFN[1]=LOW[1],把棧中節點全部取出,組成一個強連通分量{1,2,3,4}。
【例】如果2跟1不成環,那麼2不會連4,(2將自己丟擲)或(2與2的子節點成環整個丟擲)
最後執行到1,再進行1的回溯,組成強連通分量{1,3,4}出棧
自此演算法結束,找到{1,2,3,4},{5},{6}三個強連通分量。
[程式碼1]不要慌全解系列
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stack>
using namespace std;
struct ss{
int v;
int next;
/*
v指節點v
next永遠指u點->上一個點的邊序(1,2,3···)
邊序 即u到其他點(上一個點)是第幾條邊(num)
上一條邊沒有就是-1
*/
}s[1000];
int head[1000];//邊序
int dfn[1000];
int low[1000];
int vis[1000];//相當於棧
int color[1000];//染色
int n,m;
int cnt;
int num;
stack<int >st;
void init()
{
memset(head,-1,sizeof(head));
memset(vis,0,sizeof(vis));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
//memset(color,0,sizeof(color));
num=0;
cnt=0;
}
void add(int u,int v)
{
s[num].v = v;
s[num].next = head[u];
head[u] = num++;
/*
將v存進去
將u->上一個點的邊序掛上next
num一直在++(總邊數不會錯)
head[u]更新當前u的邊序
如果雙向再存u掛v邊序
eg[num].v = u;
eg[num].next = head[v];
head[v] = num++;
*/
}
void Tarjan(int u)
{
st.push(u);
dfn[u] = low[u] = ++cnt;//搜尋次序號
vis[u]=1;//節點x在棧中
for(int i = head[u]; i != -1; i = s[i].next)
{
//通過對u點上一個邊序的掛鉤
//構造對連線u點的所有邊數遍歷查詢對應v點
int v = s[i].v;
if(!dfn[v])//不曾訪問過
{
Tarjan(v);//找v點
low[u] = min(low[u],low[v]);
/*
根節點的dfn值是區分不同環的值
low是為了讓這個環都等dfn[根]
low[根]可能不等dfn[根]
根的low繼承根的根的dfn
1.如果v是根節點
不論只有v一個點還是有一個環
low[v]確實永遠比low[u]大(u比v先入)
v的環low值=dfn[v]都比low[u]的大
v不對u產生影響
2.
如果v點與u點成環
那麼順著v點或v連著的點找下去
總有一個能連到根節點
low值回溯的時候繼承根的dfn值
根的dfn是這個環裡面最小的
low[v]等於dfn[根]
v對u產生影響->low[u]=low[v]
*/
}
else if(vis[v])//訪問過但還在棧中
/*
因為根u點還沒有將邊都找完
出棧的點都是根節點邊已經找完的點或者環
已經沒有與剩下的點有任何關係才能出
*/
low[u] = min(low[u],dfn[v]);
/*
這相當於根節點有兩個分叉口a,b
並且a找到已經在棧中的b
那麼這一步其實也可以寫成
low[u] = min(low[u],low[v]);
反正連到一個環了
目的是為了讓縮點與割點的程式碼一致
區分相連的環的根有不同的dfn
無向圖找割點用的
但是縮點是將一起出棧的點縮成一個點(染成一個色)
對於縮點結果都無影響
*/
}
if(dfn[u]==low[u])//找一遍再是強連通分量
{
int now;
do{ //取出包括u的環
now=st.top();
color[now]=u; //染色
vis[now]=0;
st.pop();
}while(now!=u);
}
return;
}
void out()
{
for(int i=1;i<=n;i++)
printf("%d ",i);
printf("\n");
for(int i=1;i<=n;i++)
printf("%d ",color[i]);
printf("\n");
}
int main()
{
while(~scanf("%d%d",&n,&m) && (m+n))
{
init();
int u,v;
while(m--)
{
scanf("%d%d",&u,&v);
add(u,v);
}
//為了防止一個圖裡有不相連的兩個或多個樹
for(int i=1;i<=n;i++)
if(!dfn[i])
Tarjan(i);
out();
}
return 0;
}
[程式碼2]
若只是縮點,求強連通分量染色,不與割點程式碼一致,就不需要用棧
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stack>
#include<vector>
using namespace std;
int dfn[1000];
int low[1000];//就相當於顏色,一個環一個low=dfn[根]
int vis[1000];
int n,m;
int cnt;
stack<int >st;
vector<int >vc[1000];
void init()
{
memset(vis,0,sizeof(vis));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
cnt=0;
}
void Tarjan(int u)
{
st.push(u);
dfn[u] = low[u] = ++cnt;
vis[u]=1;
for(int i = 0; i < vc[u].size(); i++)
{
int v = vc[u][i];
if(!dfn[v])
{
Tarjan(v);
low[u] = min(low[u],low[v]);
}
else
low[u] = min(low[u],low[v]);
}
return;
}
void out()
{
for(int i=1;i<=n;i++)
printf("%d ",i);
printf("\n");
for(int i=1;i<=n;i++)
printf("%d ",low[i]);
printf("\n");
}
int main()
{
while(~scanf("%d%d",&n,&m) && (m+n))
{
init();
int u,v;
while(m--)
{
scanf("%d%d",&u,&v);
vc[u].push_back(v);
}
for(int i=1;i<=n;i++)
if(!dfn[i])
Tarjan(i);
out();
}
return 0;
}
相關文章
- Tarjan 求有向圖的強連通分量
- 【模板】tarjan 強連通分量縮點
- 強連通分量(Tarjan演算法)演算法
- Tarjan演算法(強連通分量分解)演算法
- 強聯通分量tarjan
- 圖論——強連通分量(Tarjan演算法)圖論演算法
- kosaraju 和 tarjan演算法詳解(強連通分量)演算法
- 尋找圖的強連通分量:tarjan演算法簡單理解演算法
- 強連通分量
- 20行程式碼實現,使用Tarjan演算法求解強連通分量行程演算法
- UVA1327 && POJ1904 King's Quest(tarjan+巧妙建圖+強連通分量+縮點)
- POJ 1236 Network of Schools 強連通分量
- 無向連通圖點雙連通分量
- 無向連通圖邊雙連通分量
- 連通圖與Tarjan演算法演算法
- 有向圖的強連通分量 模版
- 強連通------tarjan演算法詳解及與縮點聯合運用演算法
- UVA-11504 - Dominos(有向圖的強連通分量)
- Day7 割點、割邊和強連通分量
- 【演算法學習】tarjan 強連通、點雙、邊雙及其縮點 重磅來襲!!!!演算法
- 強連通分量及縮點 演算法解析及例題演算法
- 邊分治維護強連通分量(CF1989F,P5163)
- tarjan演算法求scc & 縮點演算法
- Day8 雙連通分量
- 連通圖演算法詳解之① :Tarjan 和 Kosaraju 演算法演算法
- 抓間諜(強連通)
- 強聯通分量及縮點法
- Tarjan(連通性相關) 筆記筆記
- 「學習筆記」tarjan 求最近公共祖先筆記
- 有向圖的連通性(判強連通)
- POJ 3694 Network 邊雙連通分量+LCA
- The Bottom of a Graph-POJ2553強連通
- 無向連通圖求割點和橋
- 【Tarjan SCC 加邊使得所有圖聯通 至少選取多少個點能圖聯通 】Network of Schools加強版.md
- 【筆記/模板】無向圖的雙連通分量筆記
- Tarjan
- 「學習筆記」雙連通分量、割點與橋筆記
- tarjan[模板]