日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

强联通分量算法的个人详解Tarjan算法(包含缩点)

發(fā)布時(shí)間:2025/3/15 编程问答 18 豆豆
生活随笔 收集整理的這篇文章主要介紹了 强联通分量算法的个人详解Tarjan算法(包含缩点) 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

有向圖強(qiáng)連通分量:在有向圖G中,如果兩個(gè)頂點(diǎn)vi,vj間(vi>vj)有一條從vi到vj的有向路徑,同時(shí)還有一條從vj到vi的有向路徑,則稱兩個(gè)頂點(diǎn)強(qiáng)連通(strongly connected)。如果有向圖G的每?jī)蓚€(gè)頂點(diǎn)都強(qiáng)連通,稱G是一個(gè)強(qiáng)連通圖。有向圖的極大強(qiáng)連通子圖,稱為強(qiáng)連通分量(strongly connected components)。

以下是引用大佬的理解:........................

然后我們理解定義:

既然我們現(xiàn)在已經(jīng)了解了什么是強(qiáng)連通,和什么是強(qiáng)連通分量,可能大家對(duì)于定義還是理解的不透徹,我們不妨引入一個(gè)圖加強(qiáng)大家對(duì)強(qiáng)連通分量和強(qiáng)連通的理解:

標(biāo)注棕色線條框框的三個(gè)部分就分別是一個(gè)強(qiáng)連通分量,也就是說(shuō),這個(gè)圖中的強(qiáng)連通分量有3個(gè)。

其中我們分析最左邊三個(gè)點(diǎn)的這部分:

其中1能夠到達(dá)0,0也能夠通過(guò)經(jīng)過(guò)2的路徑到達(dá)1.1和0就是強(qiáng)連通的。

其中1能夠通過(guò)0到達(dá)2,2也能夠到達(dá)1,那么1和2就是強(qiáng)連通的。

...

...

...

同理,我們能夠看得出來(lái)這一部分確實(shí)是強(qiáng)連通分量,也就是說(shuō),強(qiáng)連通分量里邊的任意兩個(gè)點(diǎn),都是互相可達(dá)的。


那么如何求強(qiáng)連通分量的個(gè)數(shù)呢?另外強(qiáng)連通算法能夠?qū)崿F(xiàn)什么一些基本操作呢?我們繼續(xù)詳解、


接著我們開(kāi)始接觸算法,討論如何用Tarjan算法求強(qiáng)連通分量個(gè)數(shù):

Tarjan算法,是一個(gè)基于Dfs的算法(如果大家還不知道什么是Dfs,自行百度學(xué)習(xí)),假設(shè)我們要先從0號(hào)節(jié)點(diǎn)開(kāi)始Dfs,我們發(fā)現(xiàn)一次Dfs我萌就能遍歷整個(gè)圖(樹(shù)),而且我們發(fā)現(xiàn),在Dfs的過(guò)程中,我們深搜到 了其他強(qiáng)連通分量中,那么俺們Dfs之后如何判斷他喵的哪個(gè)和那些節(jié)點(diǎn)屬于一個(gè)強(qiáng)連通分量呢?我們首先引入兩個(gè)數(shù)組:

①dfn【】

②low【】

第一個(gè)數(shù)組dfn我們用來(lái)標(biāo)記當(dāng)前節(jié)點(diǎn)在深搜過(guò)程中是第幾個(gè)遍歷到的點(diǎn)。第二個(gè)數(shù)組是整個(gè)算法核心數(shù)組,我們稍后再說(shuō),這個(gè)時(shí)候我們不妨在紙上畫一畫寫一寫,搞出隨意一個(gè)Dfs出來(lái)的dfn數(shù)組來(lái)觀察一下(假設(shè)我們從節(jié)點(diǎn)0開(kāi)始的Dfs,其中一種可能的結(jié)果是這樣滴):



這個(gè)時(shí)候我們回頭來(lái)看第二個(gè)數(shù)組要怎樣操作,我們定義low【u】=min(low【u】,low【v】(即使v搜過(guò)了也要進(jìn)行這步操作,但是v一定要在棧內(nèi)才行)),u代表當(dāng)前節(jié)點(diǎn),v代表其能到達(dá)的節(jié)點(diǎn)。這個(gè)數(shù)組在剛剛到達(dá)節(jié)點(diǎn)u的時(shí)候初始化:low【u】=dfn【u】。然后在進(jìn)行下一層深搜之后回溯回來(lái)的時(shí)候,維護(hù)low【u】。如果我們發(fā)現(xiàn)了某個(gè)節(jié)點(diǎn)回溯之后的low【u】值還是==dfn【u】的值,那么這個(gè)節(jié)點(diǎn)無(wú)疑就是一個(gè)關(guān)鍵節(jié)點(diǎn):從這個(gè)節(jié)點(diǎn)能夠到達(dá)其強(qiáng)連通分量中的其他節(jié)點(diǎn),但是沒(méi)有其他屬于這個(gè)強(qiáng)連通分量以外的點(diǎn)能夠到達(dá)這個(gè)點(diǎn),所以這個(gè)點(diǎn)的low【u】值維護(hù)完了之后還是和dfn【u】的值一樣,口述可能理解還是相對(duì)費(fèi)勁一些,我們走一遍流程圖:


①首先進(jìn)入0號(hào)節(jié)點(diǎn),初始化其low【0】=dfn【0】=1,然后深搜到節(jié)點(diǎn)2,初始化其:low【2】=dfn【2】=2,然后深搜到節(jié)點(diǎn)1,初始化其:low【1】=dfn【1】=3;

②然后從節(jié)點(diǎn)1開(kāi)始繼續(xù)深搜,發(fā)現(xiàn)0號(hào)節(jié)點(diǎn)已經(jīng)搜過(guò)了,沒(méi)有繼續(xù)能夠搜的點(diǎn)了,開(kāi)始回溯維護(hù)其值。low【1】=min(low【1】,low【0】)=1;low【2】=min(low【2】,low【1】)=1;low【0】=min(low【0】,low【2】)=1;

這個(gè)時(shí)候猛然發(fā)現(xiàn),low【0】==dfn【0】,這個(gè)時(shí)候不要太開(kāi)心,就斷定一定0號(hào)節(jié)點(diǎn)是一個(gè)關(guān)鍵點(diǎn),別忘了,這個(gè)時(shí)候還有3號(hào)節(jié)點(diǎn)沒(méi)有遍歷,我們只有在其能夠到達(dá)的節(jié)點(diǎn)全部判斷完之后,才能夠下結(jié)論,所以我們繼續(xù)Dfs。

④繼續(xù)深搜到3號(hào)節(jié)點(diǎn),初始化其low【3】=dfn【3】=4,然后深搜到4號(hào)節(jié)點(diǎn),初始化其:low【4】=dfn【4】=5,這個(gè)時(shí)候發(fā)現(xiàn)深搜到底,回溯,因?yàn)楣?jié)點(diǎn)4沒(méi)有能夠到達(dá)的點(diǎn),所以low【4】也就沒(méi)有幸進(jìn)行維護(hù)即:low【4】=dfn【4】(這個(gè)點(diǎn)一定是強(qiáng)連通分量的關(guān)鍵點(diǎn),但是我們先忽略這個(gè)點(diǎn),這個(gè)點(diǎn)沒(méi)有代表性,一會(huì)分析關(guān)鍵點(diǎn)的問(wèn)題),然后回溯到3號(hào)節(jié)點(diǎn),low【3】=min(low【3】,low【4】)=4;發(fā)現(xiàn)low【3】==dfn【3】那么這個(gè)點(diǎn)也是個(gè)關(guān)鍵點(diǎn),我們同樣忽略掉。

⑤最終回溯到節(jié)點(diǎn)0,進(jìn)行最后一次值的維護(hù):low【0】=min(low【0】,low【3】)=0,這個(gè)時(shí)候我們猛然發(fā)現(xiàn)其dfn【0】==low【0】,根據(jù)剛才所述,那么這個(gè)點(diǎn)就是一個(gè)關(guān)鍵點(diǎn):能夠遍歷其屬?gòu)?qiáng)連通分量的點(diǎn)的起始點(diǎn),而且沒(méi)有其他點(diǎn)屬于其他強(qiáng)連通分量能夠有一條有向路徑連到這個(gè)節(jié)點(diǎn)來(lái)的節(jié)點(diǎn)

※※大家仔細(xì)理解一下這句話,因?yàn)檫@個(gè)點(diǎn)屬于一個(gè)強(qiáng)連通分量,而且強(qiáng)連通分量中的任意兩個(gè)節(jié)點(diǎn)都是互達(dá)的,也就是說(shuō)強(qiáng)連通分量中一定存在環(huán),這個(gè)最后能夠回到0號(hào)節(jié)點(diǎn)的1號(hào)節(jié)點(diǎn)一定有機(jī)會(huì)維護(hù)low【1】,因?yàn)?號(hào)節(jié)點(diǎn)是先進(jìn)來(lái)的,所以其low【1】的值也一定會(huì)跟著變小,然后在回溯的過(guò)程中,其屬一個(gè)強(qiáng)連通分量的所有點(diǎn)都會(huì)將low【u】值維護(hù)成low【0】,所以這個(gè)0號(hào)節(jié)點(diǎn)就是這個(gè)關(guān)鍵點(diǎn):能夠遍歷其屬?gòu)?qiáng)連通分量的起始點(diǎn)而且這樣的起始點(diǎn)一定只有一個(gè),所以只要發(fā)現(xiàn)了一個(gè)這樣的關(guān)鍵起始點(diǎn),那么就一定發(fā)現(xiàn)了一個(gè)強(qiáng)連通分量。而且這個(gè)節(jié)點(diǎn)沒(méi)有其他點(diǎn)屬于其他強(qiáng)連通分量能夠有一條有向路徑連到這個(gè)節(jié)點(diǎn)來(lái)的節(jié)點(diǎn):如果這樣的點(diǎn)存在,那么這些個(gè)點(diǎn)應(yīng)該屬于同一個(gè)強(qiáng)連通分量。


那么綜上所述,相信大家也就能夠理解為什么dfn【u】==low【u】的時(shí)候,我們就可以判斷我們發(fā)現(xiàn)了一個(gè)強(qiáng)連通分量了。


代碼實(shí)現(xiàn):

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++; } } 然后再給一份完整代碼,附加兩組數(shù)據(jù),大家可以參考一下:
#include<stdio.h>//此代碼僅供參考,用于求一個(gè)圖存在多少個(gè)強(qiáng)連通分量 #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(); }

接下來(lái)我們討論一下Tarjan算法能夠干一些什么:

既然我們知道,Tarjan算法相當(dāng)于在一個(gè)有向圖中找有向環(huán),那么我們Tarjan算法最直接的能力就是縮點(diǎn)辣!縮點(diǎn)基于一種染色實(shí)現(xiàn),我們?cè)贒fs的過(guò)程中,嘗試把屬于同一個(gè)強(qiáng)連通分量的點(diǎn)都染成一個(gè)顏色,那么同一個(gè)顏色的點(diǎn),就相當(dāng)于一個(gè)點(diǎn)。比如剛才的實(shí)例圖中縮點(diǎn)之后就可以變成這樣:



將一個(gè)有向帶環(huán)圖變成了一個(gè)有向無(wú)環(huán)圖(DAG圖)。很多算法要基于有向無(wú)環(huán)圖才能進(jìn)行的算法就需要使用Tarjan算法實(shí)現(xiàn)染色縮點(diǎn),建一個(gè)DAG圖然后再進(jìn)行算法處理。在這種場(chǎng)合,Tarjan算法就有了很大的用武之地辣!


那么這個(gè)時(shí)候 ,我們?cè)僖胍粋€(gè)數(shù)組color【i】表示節(jié)點(diǎn)i的顏色,再引入一個(gè)數(shù)組stack【】實(shí)現(xiàn)一個(gè)棧,然后在Dfs過(guò)程中每一次遇到點(diǎn)都將點(diǎn)入棧,在每一次遇到關(guān)鍵點(diǎn)的時(shí)候?qū)?nèi)元素彈出,一直彈到棧頂元素是關(guān)鍵點(diǎn)的時(shí)候?yàn)橹?#xff0c;對(duì)這些彈出來(lái)的元素進(jìn)行染色即可。


代碼實(shí)現(xiàn):


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); } }

最后我們?cè)偕弦坏览}供大家學(xué)習(xí):

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


題目大意:給你一堆點(diǎn),一堆邊,讓你找到縮點(diǎn)之后出度為0的節(jié)點(diǎn), 然后將節(jié)點(diǎn)編號(hào)從小到大排序輸出。


思路:Tarjan,縮點(diǎn)染色,判斷出度為0的強(qiáng)連通分量,將整個(gè)集合排序,輸出即可。


AC代碼:

#include<stdio.h> #include<string.h> #include<vector> #include<algorithm> using namespace std; #define maxn 1000000 vector<int >mp[maxn]; int ans[maxn]; int degree[maxn]; int color[maxn]; int stack[maxn]; int vis[maxn]; int dfn[maxn]; int low[maxn]; int n,m,tt,cnt,sig; void init() { memset(degree,0,sizeof(degree)); memset(color,0,sizeof(color)); memset(stack,0,sizeof(stack)); 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++; 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 { color[stack[tt]]=sig; vis[stack[tt]]=-1; } while(stack[tt--]!=u); } } void Slove() { tt=-1;cnt=1;sig=0; for(int i=1;i<=n;i++) { if(vis[i]==0) { Tarjan(i); } } for(int i=1;i<=n;i++) { for(int j=0;j<mp[i].size();j++) { int v=mp[i][j]; if(color[i]!=color[v]) { degree[color[i]]++; } } } int tot=0; for(int i=1;i<=sig;i++) { if(degree[i]>0)continue; for(int j=1;j<=n;j++) { if(color[j]==i) { ans[tot++]=j; } } } sort(ans,ans+tot); for(int i=0;i<tot;i++) { if(i==0)printf("%d",ans[i]); else printf(" %d",ans[i]); } printf("\n"); } 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(); } }

總結(jié)

以上是生活随笔為你收集整理的强联通分量算法的个人详解Tarjan算法(包含缩点)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。