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算法]

      Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。

      定义DFN(u)为节点u搜索的次序编号(时间戳),Low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号。
        当DFN(u)=Low(u)时,以u为根的搜索子树上所有节点是一个强连通分量。

    [演示]

      从节点1开始DFS,把遍历到的节点加入栈中。搜索到节点u=6时,DFN[6]=LOW[6],找到了一个强连通分量。退栈到u=v为止,{6}为一个强连通分量。

    wps_clip_image-16442

      返回节点5,发现DFN[5]=LOW[5],退栈后{5}为一个强连通分量。

    wps_clip_image-24939

      返回节点3,继续搜索到节点4,把4加入堆栈。发现节点4向节点1有后向边,节点1还在栈中,所以LOW[4]=1。节点6已经出栈,(4,6)是横叉边,返回3,(3,4)为树枝边,所以LOW[3]=LOW[4]=1。

    wps_clip_image-17734

      继续回到节点1,最后访问节点2。访问边(2,4),4还在栈中,所以LOW[2]=DFN[4]=5。返回1后,发现DFN[1]=LOW[1],把栈中节点全部取出,组成一个连通分量{1,3,4,2}。

    wps_clip_image-10846

      算法结束。求出了图中全部的三个强连通分量{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 }

    [例子]

      题目:高速公路

      问题描述
        某国有n个城市,为了使得城市间的交通更便利,该国国王打算在城市之间修一些高速公路,由于经费限制,国王打算第一阶段先在部分城市之间修一些单向的高速公路。
        现在,大臣们帮国王拟了一个修高速公路的计划。看了计划后,国王发现,有些城市之间可以通过高速公路直接(不经过其他城市)或间接(经过一个或多个其他城市)到达,而有的却不能。如果城市A可以通过高速公路到达城市B,而且城市B也可以通过高速公路到达城市A,则这两个城市被称为便利城市对。
        国王想知道,在大臣们给他的计划中,有多少个便利城市对。
      输入格式
        输入的第一行包含两个整数n, m,分别表示城市和单向高速公路的数量。
        接下来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 }

      

  • 相关阅读:
    Kubernetes 服务入口管理 Traefik Ingress Controller
    flex的titlewindow如何自适应浏览器的宽度和高度
    JQuery的事件中使用this
    jQuery控制 input 不可编辑
    jquery 操作 input显示或者隐藏
    Word 创建模板
    HTML转PDF
    SQL server 自增主键重新从1开始
    读取 .properties文件到数据库
    根据json生成java实体类文件
  • 原文地址:https://www.cnblogs.com/whzlw/p/5042023.html
Copyright © 2011-2022 走看看