zoukankan      html  css  js  c++  java
  • 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

     

  • 相关阅读:
    idea 配置 maven 项目
    idea 配置普通web项目
    idea java.lang.OutOfMemoryError: PermGen space
    idea checkout 项目
    物理机自动化装机实现
    prometheus node_exporter相关监控指标
    nginx Linux内核参数的优化
    《高性能 Go 代码工坊》中译
    docker仓库资源的地址修改
    influxdb-1.7.8(centos 7) 部署
  • 原文地址:https://www.cnblogs.com/haimishasha/p/5339405.html
Copyright © 2011-2022 走看看