• 强连通分量(tarjan求强连通分量)


    双DFS方法就是正dfs扫一遍,然后将边反向dfs扫一遍。《挑战程序设计》上有说明。

    双dfs代码:

     1 #include <iostream>
     2 #include <cstdio>
     3 #include <cstring>
     4 #include <vector>
     5 
     6 using namespace std;
     7 const int MAXN = 1e4 + 5;
     8 vector <int> G[MAXN]; //图的邻接表
     9 vector <int> RG[MAXN]; //图的反向邻接表
    10 vector <int> vs; //后序遍历的顶点顺序表
    11 bool ok[MAXN]; //访问标记
    12 int node[MAXN]; //所属强连通分量的序号
    13 //第一次dfs
    14 void dfs(int u) {
    15     ok[u] = true;
    16     for(int i = 0 ; i < G[u].size() ; i++) {
    17         if(!ok[G[u][i]]) 
    18             dfs(G[u][i]);
    19     }
    20     vs.push_back(u);
    21 }
    22 //第二次dfs
    23 void rdfs(int u , int k) {
    24     node[u] = k;
    25     ok[u] = true;
    26     for(int i = 0 ; i < RG[u].size() ; i++) {
    27         if(!ok[RG[u][i]])
    28             rdfs(RG[u][i] , k);
    29     }
    30 }
    31 
    32 int main()
    33 {
    34     int n , m , u , v;
    35     while(~scanf("%d %d" , &n , &m) && (n || m)) {
    36         for(int i = 1 ; i <= n ; i++) {
    37             G[i].clear();
    38             RG[i].clear();
    39             ok[i] = false;
    40             vs.clear();
    41         }
    42         for(int i = 0 ; i < m ; i++) {
    43             scanf("%d %d" , &u , &v);
    44             G[u].push_back(v);
    45             RG[v].push_back(u);
    46         }
    47         //第一次dfs 选取任意的顶点作为起点遍历 后序遍历 越接近图尾部(叶子)的顶点顺序越小
    48         for(int i = 1 ; i <= n ; i++) {
    49             if(!ok[i]) 
    50                 dfs(i);
    51         }
    52         memset(ok , false , sizeof(ok));
    53         int k = 0;
    54         //第二次dfs 将边反向遍历 以标记最大的顶点作为起点遍历 这样便可以给强连通分量标号
    55         for(int i = vs.size() - 1 ; i >= 0 ; i--) {
    56             if(!ok[vs[i]])
    57                 rdfs(vs[i] , ++k);
    58         }
    59         if(k > 1) {
    60             printf("No
    ");
    61         }
    62         else {
    63             printf("Yes
    ");
    64         }
    65     }
    66 }

    tarjan代码虽然比双dfs多,理解也稍微复杂,但是用处比较多,像求LCA 桥 scc之类的问题。

    推荐一个学scc的blog,讲的很好。 https://www.byvoid.com/blog/tag/%E5%9C%96%E8%AB%96

    代码如下:

     1 //以hdu1269为例
     2 //强连通分量 tarjan算法
     3 #include <iostream>
     4 #include <cstdio>
     5 #include <cstring>
     6 #include <vector>
     7 using namespace std;
     8 const int MAXN = 1e4 + 5;
     9 vector <int> G[MAXN]; //临接表
    10 bool instack[MAXN];  //i是否还在在栈里
    11 int sccnum , index; //连通分量数 和时间顺序
    12 int top , st[MAXN]; //存储i的模拟栈
    13 int block[MAXN];  //i所属的连通分量
    14 int low[MAXN] , dfn[MAXN];  //i能最早访问到的点 和时间戳
    15 
    16 void tarjan(int u) {
    17     st[++top] = u;
    18     instack[u] = true;
    19     low[u] = dfn[u] = ++index;
    20     for(int i = 0 ; i < G[u].size() ; i++) {
    21         int v = G[u][i];
    22         if(!dfn[v]) { //v点没访问过
    23             tarjan(v);
    24             if(low[v] < low[u]) { //回溯上来  low[v]表示的时间戳(连通分量的根)  u和v在一个连通分量里
    25                 low[u] = low[v];
    26             }
    27         }
    28         else if(instack[v]) {  //v还在栈里 说明u和v在一个连通分量(形成环路) v相当于连通分量的一个根
    29             low[u] = min(low[u] , dfn[v]);
    30         }
    31     }
    32     int v;
    33     if(dfn[u] == low[u]) {  //是连通分量的根
    34         sccnum++;
    35         do {
    36             v = st[top--];
    37             block[v] = sccnum;
    38             instack[v] = false;
    39         }while(v != u); //连通分量的所有的点
    40     }
    41 }
    42 
    43 void init(int n) {
    44     for(int i = 1 ; i <= n ; i++) {
    45         G[i].clear();
    46         instack[i] = false;
    47         dfn[i] = 0;
    48     }
    49     sccnum = index = top = 0;
    50 }
    51 
    52 int main()
    53 {
    54     int n , m , u , v;
    55     while(~scanf("%d %d" , &n , &m) && (n || m)) {
    56         init(n);
    57         for(int i = 0 ; i < m ; i++) {
    58             scanf("%d %d" , &u , &v);
    59             G[u].push_back(v);
    60         }
    61         for(int i = 1 ; i <= n ; i++) {
    62             if(!dfn[i]) {
    63                 tarjan(i);
    64             }
    65         }
    66         if(sccnum > 1) {
    67             cout << "No
    ";
    68         }
    69         else {
    70             cout << "Yes
    ";
    71         }
    72     }
    73 }
  • 相关阅读:
    app.config应该放哪?
    Connection 和Dispose的学习日志
    简单的sqlhelper的学习日志
    EF 事务(非分布式事务)
    Angularjs 地址联动2.1.1
    C# 如何物理删除有主外键约束的记录?存储过程实现
    C# 枚举基本用法及扩展方法
    JS 去除重复元素的方法
    MVC4程序运行报错
    ASP.NET MVC4 & Entity Framework 6.0 IIS 部署出错解决方案
  • 原文地址:https://www.cnblogs.com/Recoder/p/5247992.html
Copyright © 2020-2023  润新知