Tarjan算法详解

Tarjan算法详解

【概念】

  在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。非强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components)

下图中,子图{1,2,3,4}为一个强连通分量,因为顶点1,2,3,4两两可达。{5},{6}也分别是两个强连通分量。

  

【功能】

    Tarjan算法的用途之一是,求一个有向图G=(V,E)里极大强连通分量。强连通分量是指有向图G里顶点间能互相到达的子图。而如果一个强连通分量已经没有被其它强通分量完全包含的话,那么这个强连通分量就是极大强连通分量

 

【算法思想】

    用dfs遍历G中的每个顶点,通dfn[i]表示dfs时达到顶点i的时间,low[i]表示i所能直接或间接达到时间最小的顶点。(实际操作中low[i]不一定最小,但不会影响程序的最终结果)

    程序开始时,time初始化为0,在dfs遍历到v时,low[v]=dfn[v]=time++,

  v入栈(这里的栈不是dfs的递归时系统弄出来的栈)扫描一遍v所能直接达到的顶点k,

  如果 k没有被访问过那么先dfs遍历k,low[v]=min(low[v],low[k]);

  如果k在栈里,那么low[v]=min(low[v],dfn[k])(就是这里使得low[v]不一定最小,但不会影响到这里的low[v]会小于dfn[v])。

  扫描完所有的k以后,如果low[v]=dfn[v]时,栈里v以及v以上的顶点全部出栈,且刚刚出栈的就是一个极大强连通分量。

【大概的证明】

  1.在栈里,当dfs遍历到v,而且已经遍历完v所能直接到达的顶点时,low[v]=dfn[v]时,v一定能到达栈里v上面的顶点:

    因为当dfs遍历到v,而且已经dfs递归调用完v所能直接到达的顶点时(假设上面没有low=dfn),这时如果发现low[v]=dfn[v],栈上面的顶点一定是刚才从顶点v递归调用时进栈的,所以v一定能够到达那些顶点。

  2 .dfs遍历时,如果已经遍历完v所能直接到达的顶点而low[v]=dfn[v],我们知道v一定能到达栈里v上面的顶点,这些顶点的low一定小于 自己的dfn,不然就会出栈了,也不会小于dfn[v],不然low [v]一定小于dfn[v],所以栈里v以其v以上的顶点组成的子图是一个强连通分量,如果它不是极大强连通分量的话low[v]也一定小于dfn[v](这里不再详细说),所以栈里v以其v以上的顶点组成的子图是一个极大强连通分量。

【时间复杂度】

       因为所有的点都刚好进过一次栈,所有的边都访问的过一次,所以时间复杂度为O(n+m)

【算法演示】

  下面给出一个大牛写的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)。

  求有向图的强连通分量还有一个强有力的算法,为Kosaraju算法。Kosaraju是基于对有向图及其逆图两次DFS的方法,其时间复杂度也是O(N+M)。与Trajan算法相比,Kosaraju算法可能会稍微更直观一些。但是Tarjan只用对原图进行一次DFS,不用建立逆图,更简洁。在实际的测试中,Tarjan算法的运行效率也比Kosaraju算法高30%左右。此外,该Tarjan算法与求无向图的双连通分量(割点、桥)的Tarjan算法也有着很深的联系。学习该Tarjan算法,也有助于深入理解求双连通分量的Tarjan算法,两者可以类比、组合理解。

  求有向图的强连通分量的Tarjan算法是以其发明者Robert Tarjan命名的。Robert Tarjan还发明了求双连通分量的Tarjan算法,以及求最近公共祖先的离线Tarjan算法,在此对Tarjan表示崇高的敬意。

【源代码c++】

  

  1 #include <iostream>
  2 #include <stack>
  3 using namespace std;
  4 
  5 #define MAX_VERTEX_SIZE 10001
  6 struct EdgeNode{
  7     int vertex;
  8     EdgeNode *nextArc;
  9 };
 10 
 11 struct VerTexNode{
 12     EdgeNode* firstArc;
 13 };
 14 
 15 struct Graph{
 16     int n,e;
 17     VerTexNode vNode[MAX_VERTEX_SIZE];
 18 };
 19 
 20 int time = 0;
 21 int low[MAX_VERTEX_SIZE];
 22 int dfn[MAX_VERTEX_SIZE];
 23 int visited[MAX_VERTEX_SIZE];
 24 int inStack[MAX_VERTEX_SIZE];
 25 stack<int> st;
 26 Graph graph;
 27 
 28 void initeGraph(int n,int m)
 29 {
 30     for(int i = 1;i<=n;i++)
 31     {
 32         graph.vNode[i].firstArc = NULL;
 33     }
 34     graph.n = n;
 35     graph.e = m;
 36 
 37 }
 38 
 39 //头插法建立图
 40 void creatGraph(int s,int v)
 41 {
 42     EdgeNode *edgeNode = new EdgeNode;
 43     edgeNode->vertex = v;
 44     edgeNode->nextArc = graph.vNode[s].firstArc;
 45     graph.vNode[s].firstArc = edgeNode;
 46 }    
 47 
 48 int min(int a,int b)
 49 {
 50     if(a>b)
 51         return b;
 52     else
 53         return a;
 54 }
 55 
 56 void trajan(int u)
 57 {
 58     dfn[u] = low[u] = time++;
 59     st.push(u);
 60     visited[u] = 1;
 61     inStack[u] = 1;
 62     EdgeNode *edgePtr = graph.vNode[u].firstArc;
 63     while(edgePtr !=NULL)
 64     {
 65         int v = edgePtr->vertex;
 66         if(visited[v] == 0)
 67         {
 68             trajan(v);
 69             low[u] = min(low[u],low[v]);
 70         }
 71         else
 72         {
 73             low[u] = min(low[u],dfn[v]);
 74         }
 75         edgePtr = edgePtr->nextArc;
 76     }
 77 
 78     if(dfn[u] == low[u])
 79     {
 80         int vtx;
 81         cout<<"set is: ";
 82         do{
 83             vtx = st.top();
 84             st.pop();
 85             inStack[vtx] = 0;//表示已经出栈
 86             cout<<vtx<<' ';
 87         }while(vtx !=u );
 88     }
 89 
 90 }
 91 
 92 int main()
 93 {
 94     int n,m;
 95     int s,a;
 96     cin>>n>>m;
 97     initeGraph(n,m);
 98     for(int i = 1;i<=n;i++)
 99     {
100         visited[i] = 0;
101         inStack[i] = 0;
102         dfn[i] = 0;
103         low[i] = 0;
104     }
105 
106     for(int j = 1;j<=m;j++)
107     {
108         cin>>s>>a;
109         creatGraph(s,a);
110     }
111 
112     for(int i =1;i<=n;i++)
113         if(visited[i] == 0)
114             trajan(i);
115     return 0;
116 }
View Code

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值