强联通分量算法的个人详解Tarjan算法(包含缩点)
有向圖強連通分量:在有向圖G中,如果兩個頂點vi,vj間(vi>vj)有一條從vi到vj的有向路徑,同時還有一條從vj到vi的有向路徑,則稱兩個頂點強連通(strongly connected)。如果有向圖G的每兩個頂點都強連通,稱G是一個強連通圖。有向圖的極大強連通子圖,稱為強連通分量(strongly connected components)。
以下是引用大佬的理解:........................
然后我們理解定義:
既然我們現在已經了解了什么是強連通,和什么是強連通分量,可能大家對于定義還是理解的不透徹,我們不妨引入一個圖加強大家對強連通分量和強連通的理解:
標注棕色線條框框的三個部分就分別是一個強連通分量,也就是說,這個圖中的強連通分量有3個。
其中我們分析最左邊三個點的這部分:
其中1能夠到達0,0也能夠通過經過2的路徑到達1.1和0就是強連通的。
其中1能夠通過0到達2,2也能夠到達1,那么1和2就是強連通的。
...
...
...
同理,我們能夠看得出來這一部分確實是強連通分量,也就是說,強連通分量里邊的任意兩個點,都是互相可達的。
那么如何求強連通分量的個數呢?另外強連通算法能夠實現什么一些基本操作呢?我們繼續詳解、
接著我們開始接觸算法,討論如何用Tarjan算法求強連通分量個數:
Tarjan算法,是一個基于Dfs的算法(如果大家還不知道什么是Dfs,自行百度學習),假設我們要先從0號節點開始Dfs,我們發現一次Dfs我萌就能遍歷整個圖(樹),而且我們發現,在Dfs的過程中,我們深搜到 了其他強連通分量中,那么俺們Dfs之后如何判斷他喵的哪個和那些節點屬于一個強連通分量呢?我們首先引入兩個數組:
①dfn【】
②low【】
第一個數組dfn我們用來標記當前節點在深搜過程中是第幾個遍歷到的點。第二個數組是整個算法核心數組,我們稍后再說,這個時候我們不妨在紙上畫一畫寫一寫,搞出隨意一個Dfs出來的dfn數組來觀察一下(假設我們從節點0開始的Dfs,其中一種可能的結果是這樣滴):
這個時候我們回頭來看第二個數組要怎樣操作,我們定義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】的時候,我們就可以判斷我們發現了一個強連通分量了。
代碼實現:
void Tarjan(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)Tarjan(v); if(vis[v]==1)low[u]=min(low[u],low[v]); } if(dfn[u]==low[u]) { sig++; } } 然后再給一份完整代碼,附加兩組數據,大家可以參考一下:#include<stdio.h>//此代碼僅供參考,用于求一個圖存在多少個強連通分量 #include<string.h> #include<vector> #include<algorithm> using namespace std; #define maxn 1000000 vector<int >mp[maxn]; int ans[maxn]; int vis[maxn]; int dfn[maxn]; int low[maxn]; int n,m,tt,cnt,sig; 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 Tarjan(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)Tarjan(v); if(vis[v]==1)low[u]=min(low[u],low[v]); } if(dfn[u]==low[u]) { sig++; } } void Slove() { tt=-1;cnt=1;sig=0; for(int i=1;i<=n;i++) { if(vis[i]==0) { Tarjan(i); } } printf("%d\n",sig); } int main() { while(~scanf("%d",&n)) { 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(); }
接下來我們討論一下Tarjan算法能夠干一些什么:
既然我們知道,Tarjan算法相當于在一個有向圖中找有向環,那么我們Tarjan算法最直接的能力就是縮點辣!縮點基于一種染色實現,我們在Dfs的過程中,嘗試把屬于同一個強連通分量的點都染成一個顏色,那么同一個顏色的點,就相當于一個點。比如剛才的實例圖中縮點之后就可以變成這樣:
將一個有向帶環圖變成了一個有向無環圖(DAG圖)。很多算法要基于有向無環圖才能進行的算法就需要使用Tarjan算法實現染色縮點,建一個DAG圖然后再進行算法處理。在這種場合,Tarjan算法就有了很大的用武之地辣!
那么這個時候 ,我們再引入一個數組color【i】表示節點i的顏色,再引入一個數組stack【】實現一個棧,然后在Dfs過程中每一次遇到點都將點入棧,在每一次遇到關鍵點的時候將棧內元素彈出,一直彈到棧頂元素是關鍵點的時候為止,對這些彈出來的元素進行染色即可。
代碼實現:
void Tarjan(int u)//此代碼僅供參考 { vis[u]=1; low[u]=dfn[u]=cnt++; stack[++tt]=u; for(int i=0;i<mp[u].size();i++) { int v=mp[u][i]; if(vis[v]==0)Tarjan(v); if(vis[v]==1)low[u]=min(low[u],low[v]); } if(dfn[u]==low[u]) { sig++; do { low[stack[tt]]=sig; color[stack[tt]]=sig; vis[stack[tt]]=-1; } while(stack[tt--]!=u); } }
最后我們再上一道例題供大家學習:
Poj 2553
The Bottom of a Graph
Time Limit:?3000MS | ? | Memory Limit:?65536K |
Total Submissions:?10099 | ? | Accepted:?4175 |
Description
We will use the following (standard) definitions from graph theory. Let?V?be a nonempty and finite set, its elements being called vertices (or nodes). Let?E?be a subset of the Cartesian product?V×V, its elements being called edges. Then?G=(V,E)?is called a directed graph.?
Let?n?be a positive integer, and let?p=(e1,...,en)?be a sequence of length?n?of edges?ei∈E?such that?ei=(vi,vi+1)?for a sequence of vertices?(v1,...,vn+1). Then?p?is called a path from vertex?v1?to vertex?vn+1?in?G?and we say that?vn+1?is reachable from?v1, writing?(v1→vn+1).?
Here are some new definitions. A node?v?in a graph?G=(V,E)?is called a sink, if for every node?w?in?G?that is reachable from?v,?v?is also reachable from?w. The bottom of a graph is the subset of all nodes that are sinks, i.e.,bottom(G)={v∈V|?w∈V:(v→w)?(w→v)}. You have to calculate the bottom of certain graphs.
Input
The input contains several test cases, each of which corresponds to a directed graph?G. Each test case starts with an integer number?v, denoting the number of vertices of?G=(V,E), where the vertices will be identified by the integer numbers in the set?V={1,...,v}. You may assume that?1<=v<=5000. That is followed by a non-negative integer?e?and, thereafter,?e?pairs of vertex identifiers?v1,w1,...,ve,we?with the meaning that?(vi,wi)∈E. There are no edges other than specified by these pairs. The last test case is followed by a zero.
Output
For each test case output the bottom of the specified graph on a single line. To this end, print the numbers of all nodes that are sinks in sorted order separated by a single space character. If the bottom is empty, print an empty line.
Sample Input
3 3
1 3 2 3 3 1
2 1
1 2
0
Sample Output
1 3
2
Source
Ulm Local 2003
題目大意:給你一堆點,一堆邊,讓你找到縮點之后出度為0的節點, 然后將節點編號從小到大排序輸出。
思路:Tarjan,縮點染色,判斷出度為0的強連通分量,將整個集合排序,輸出即可。
AC代碼:
總結
以上是生活随笔為你收集整理的强联通分量算法的个人详解Tarjan算法(包含缩点)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JAVA爬取亚马逊的商品信息
- 下一篇: Netty入门(一)环境搭建及使用