[有向图强连通分量]
有向图强连通分量的Tarjan算法
在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。非强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components)。
下图中,子图{1,2,3,4}为一个强连通分量,因为顶点1,2,3,4两两可达。{5},{6}也分别是两个强连通分量。
[Tarjan算法]
Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。
[演示]
从节点1开始DFS,把遍历到的节点加入栈中。搜索到节点u=6时,DFN[6]=LOW[6],找到了一个强连通分量。退栈到u=v为止,{6}为一个强连通分量。
返回节点5,发现DFN[5]=LOW[5],退栈后{5}为一个强连通分量。
返回节点3,继续搜索到节点4,把4加入堆栈。发现节点4向节点1有后向边,节点1还在栈中,所以LOW[4]=1。节点6已经出栈,(4,6)是横叉边,返回3,(3,4)为树枝边,所以LOW[3]=LOW[4]=1。
继续回到节点1,最后访问节点2。访问边(2,4),4还在栈中,所以LOW[2]=DFN[4]=5。返回1后,发现DFN[1]=LOW[1],把栈中节点全部取出,组成一个连通分量{1,3,4,2}。
算法结束。求出了图中全部的三个强连通分量{1,3,4,2},{5},{6}。
可以发现,运行Tarjan算法的过程中,每个顶点都被访问了一次,且只进出了一次堆栈,每条边也只被访问了一次,所以该算法的时间复杂度为O(N+M)。
[拓展]
有时对求出的强连通分量需要做缩点操作,即将所有分量缩成一个点,常用的是使用出入读。当然有时也得用缩点构建图。
[模板]
1 /*==================================================*\ 2 | Tarjan强连通分量 3 | INIT: vec[]为邻接表; stop, cnt, scnt,inStack置0; id[],pre[]置-1; 4 | CALL: for(i=0; i<n; ++i) if(-1==dfn[i]) tarjan(i); 5 | 6 | vec[] :邻接表 7 | id[] :属于哪个分量,对应范围:0~cnt-1 8 | dfn[m]:记录m的是第几次访问,同时如果dfn[m]==-1则表明m未访问 9 | vec[] :为邻接表 10 | s[] :栈 11 | stop :栈顶指针 12 | scnt :强连通分量的个数 13 | cnt_scnt[] :强连通分量元素个数 14 | cnt :访问计数 15 | void tarjan(int v) :v为当前访问节点 16 \*==================================================*/ 17 const int V=10010; 18 vector<int> vec[V]; 19 int n; 20 int id[V],dfn[V],s[V],low[V],stop=0,cnt=0,scnt=0,cnt_scnt[V]; 21 bool inStack[V]; 22 void tarjan(int v) 23 { 24 int t; 25 low[v]=dfn[v]=cnt++; 26 s[stop++]=v; 27 inStack[v]=1; 28 vector<int>::iterator pv; 29 for(pv=vec[v].begin();pv!=vec[v].end();++pv) 30 { 31 if(-1==dfn[*pv]) //未访问过 32 { 33 tarjan(*pv); 34 low[v]=min(low[v],low[*pv]); 35 } 36 else if(inStack[*pv]) //在栈中 37 low[v]=min(low[v],low[*pv]); 38 } 39 if(dfn[v]==low[v]) //找到分量 40 { 41 do{ 42 t=s[--stop]; 43 id[t]=scnt; 44 inStack[t]=false; 45 cnt_scnt[scnt]++; 46 }while(t!=v); 47 ++scnt; 48 } 49 }
[例子]
题目:高速公路
现在,大臣们帮国王拟了一个修高速公路的计划。看了计划后,国王发现,有些城市之间可以通过高速公路直接(不经过其他城市)或间接(经过一个或多个其他城市)到达,而有的却不能。如果城市A可以通过高速公路到达城市B,而且城市B也可以通过高速公路到达城市A,则这两个城市被称为便利城市对。
国王想知道,在大臣们给他的计划中,有多少个便利城市对。
接下来m行,每行两个整数a, b,表示城市a有一条单向的高速公路连向城市b。
1 #include<iostream> 2 #include<cstring> 3 #include<vector> 4 #include<iterator> 5 using namespace std; 6 const int V=10010; 7 vector<int> vec[V]; 8 int n; 9 int id[V],dfn[V],s[V],low[V],stop=0,cnt=0,scnt=0,cnt_scnt[V]; 10 bool inStack[V]; 11 void tarjan(int v) 12 { 13 int t; 14 low[v]=dfn[v]=cnt++; 15 s[stop++]=v; 16 inStack[v]=1; 17 vector<int>::iterator pv; 18 for(pv=vec[v].begin();pv!=vec[v].end();++pv) 19 { 20 if(-1==dfn[*pv]) 21 { 22 tarjan(*pv); 23 low[v]=min(low[v],low[*pv]); 24 } 25 else if(inStack[*pv]) 26 low[v]=min(low[v],low[*pv]); 27 } 28 if(dfn[v]==low[v]) //找到分量 29 { 30 do{ 31 t=s[--stop]; 32 id[t]=scnt; 33 inStack[t]=false; 34 cnt_scnt[scnt]++; 35 }while(t!=v); 36 ++scnt; 37 } 38 } 39 int main() 40 { 41 int m; 42 cin>>n>>m; 43 int a,b; 44 memset(inStack,false,sizeof(inStack)); 45 memset(dfn,-1,sizeof(dfn)); 46 memset(id,-1,sizeof(id)); 47 memset(cnt_scnt,0,sizeof(cnt_scnt)); 48 stop=cnt=scnt=0; 49 for(int i=0;i<m;i++) 50 { 51 cin>>a>>b; 52 vec[a-1].push_back(b-1); 53 } 54 for(int i=0;i<n;i++) 55 { 56 if(-1==dfn[i]) 57 tarjan(i); 58 } 59 int ans=0; 60 for(int i=0;i<scnt;i++) 61 { 62 ans+=((cnt_scnt[i]-1)*cnt_scnt[i])/2; 63 } 64 cout<<ans; 65 }
来源:https://www.cnblogs.com/whzlw/p/5042023.html