有向圖的連通分量的求解思路
kosaraju算法
逛了很多博客,感覺都很難懂,終於找到一篇能看懂的,摘要記錄一下
原博客https://www.cnblogs.com/nullzx/p/6437926.html
關於連通分量是什么自行百度,這里主要說明連通分量的求解方法
基本思路:第一次DFS得出頂點的順序,根據頂點順序進行第二次DFS,也就是逆后序遍歷(手動模擬一下堆棧就知道第二次DFS的過程就能得出答案)。
為什么要兩次DFS?
如果從連通分量A中任意一個定點DFS,得不到正確結果。應該按照被指向的強連通分量的定點排在前面的順序進行DFS。上圖按照B3,B4,B5,A0,A1,A2的順序DFS。實際中我們只要保證被指向的強連通分量的至少一個頂點排在指向這個連通分量的所有頂點前面即可,比如B3,A0,A1,A2,B4,B5;B3排在強連通分量A所有定點的前面。
如何得到滿足要求的頂點順序:對原圖取反,從反向圖的任意節點開始進行DFS的逆后序遍歷
DFS的逆后序遍歷指:如果當前頂點沒被訪問,先遍歷完與當前頂點相連的且未被訪問的所有其他頂點,然后將當前頂點加入棧,最后從棧頂到棧底的順序是我們需要的頂點順序。
其實它是利用了有向圖的方向性:例如在上圖中,強連通分量A和B不管正反圖都能自己跑一圈,但是從A到B就只能從A2跑到B3,不可能從B3跑到A2,所以將圖取反(注意圖取反,不能從A2跑到B3,頂點順序被記錄,按頂點順序一個個彈出遍歷,之前遍歷過的點就不用再遍歷),做成反向圖,再逆后序遍歷,A0在棧頂,遍歷一圈,不能從A2跑到B3,就得到一個連通分量(如下圖)
接下來原博主的代碼看不懂,還是百度百科kosaraju算法里面的代碼好
1 #include <iostream> 2 #include <cstring> 3 #include <cmath> 4 #include <cstdio> 5 #include <stack> 6 #include <algorithm> 7 using namespace std; 8 #define INF 999999999 9 #define MAXN 551 10 #define MOD 1000000009 11 int n; 12 int mp[MAXN][MAXN], nmp[MAXN][MAXN], vis[MAXN]; 13 stack<int> s; 14 void dfs_1(int v) 15 { 16 // cout << v <<endl; 17 vis[v] = 1; 18 for(int i = 1; i <= n; ++i) 19 if(!vis[i] && mp[v][i]) 20 dfs_1(i); 21 s.push(v); 22 } 23 void dfs_2(int v) 24 { 25 vis[v] = 1; 26 for(int i = 1; i <= n; ++i) 27 if(!vis[i] && nmp[v][i]) 28 dfs_2(i); 29 } 30 int kosaraju() 31 { 32 while(!s.empty()) 33 s.pop(); 34 memset(vis, 0, sizeof(vis)); 35 36 for(int i = 1; i <= n; ++i) 37 if(!vis[i]) 38 dfs_1(i); 39 40 int ans=0; 41 memset(vis, 0, sizeof(vis)); 42 43 while(!s.empty()) 44 { 45 int v = s.top(); 46 s.pop(); 47 if(!vis[v]) 48 { 49 // cout << v <<endl; 50 ans++; 51 dfs_2(v); 52 } 53 } 54 return ans; 55 } 56 int main() 57 { 58 int m, a, b; 59 //n個點,m條邊 60 cin >> n >>m; 61 memset(mp, 0, sizeof(mp)); 62 memset(nmp, 0, sizeof(nmp)); 63 for(int i = 0; i < m; ++i) 64 { 65 cin >> a >>b; 66 mp[a][b] = 1; 67 nmp[b][a] = 1; 68 } 69 cout << kosaraju() <<endl; 70 return 0; 71 } 72 /* 73 5 5 74 1 2 75 2 1 76 2 3 77 3 4 78 4 1 79 樣例輸出: 80 2 81 */ 82
tarjan算法
這個算法網上很容易找到詳解,其實認真看一遍百度百科也就懂得七七八八了
再附詳解地址,這位博主也是轉別人的,但是那個別人的博客打不開了
https://blog.csdn.net/qq_34374664/article/details/77488976
這個算法的核心思想:將連通分量的各個點用一個點表示。
1 #include <iostream> 2 #include <cstring> 3 #include <cmath> 4 #include <cstdio> 5 #include <stack> 6 #include <algorithm> 7 8 using namespace std; 9 10 #define INF 0x3f3f3f3f 11 #define MAXN 551 12 13 int DFN[MAXN], Low[MAXN]; 14 int vis[MAXN], sta_ck[MAXN]; 15 int Index, cnt, tot; 16 int n; 17 18 struct Node 19 { 20 int to; 21 int next; 22 }node[MAXN]; 23 int head[MAXN]; 24 25 //用head作為頭指針指向下一個結點下標,讓next指向head指向的下標后,head指向該結點 26 void add(int x, int y) 27 { 28 node[++cnt].next = head[x]; 29 node[cnt].to = y; 30 head[x] = cnt; 31 } 32 33 void tarjan(int x) 34 { 35 DFN[x] = Low[x] = ++tot; 36 sta_ck[++Index] = x; 37 vis[x] = 1; 38 for(int i = head[x]; i != -1; i = node[i].next) 39 { 40 if(!DFN[node[i].to]) 41 { 42 tarjan(node[i].to); 43 Low[x] = min(Low[x], Low[node[i].to]); 44 } 45 else if(vis[node[i].to]) 46 Low[x] = min(Low[x], DFN[node[i].to]); 47 } 48 if(Low[x] == DFN[x]) 49 { 50 do 51 { 52 cout << sta_ck[Index] <<" "; 53 vis[sta_ck[Index]]=0; 54 Index--; 55 }while(x != sta_ck[Index + 1]); 56 cout << endl; 57 } 58 59 } 60 61 int main() 62 { 63 memset(head, -1, sizeof(head)); 64 memset(DFN, 0, sizeof(DFN)); 65 memset(Low, 0, sizeof(Low)); 66 int n, m; 67 cin >>n >>m; 68 int x, y; 69 for(int i = 1; i <= m; ++i) 70 { 71 cin >>x >>y; 72 add(x, y); 73 } 74 for(int i = 1; i <= n; ++i) 75 if(!DFN[i]) 76 tarjan(i); 77 return 0; 78 }