求强连通分量的三种算法——Kosaraju, Tarjan, Gabow

就我所知,有三种时间复杂度为O(n)的方法可以求强连通分量,分别是Kosaraju、Tarjan和Gabow。

  1. Kosaraju
    算法的步骤为
    • 对图G进行DFS,并按照遍历完成的先后顺序进行标号。
    • 将图G中所有的边反向得到G'。
    • 对G'进行DFS,每轮DFS都选择编号最大的点最为当前的遍历树的根。
    • 最后,遍历得到的森林就是SCC的集合。
    该算法的优点在于,最后得到的节点是按照拓扑序组织好的,在求解2-SAT的过程中十分方便。
  2. Tarjan
    第一个求解SCC的算法,应用非常广泛,几乎任何和图的遍历有关的问题都可以套用Tarjan算法的思想(比如,求割点、桥、块等等)。
    在遍历时,对每一个节点定义时间戳,同时定义Low函数,含义为节点及其子孙通过非父子边的返祖边所能到达的最小时间戳。最后若Low函数等于其时间戳,则当前递归栈内存在一个SCC。
    代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    void dfs(intindex) {
      low[index] = dfn[index] = cnt++;
      used[index] = 1;
      del[index] = 0;
      st[top++] = index;
      for (inti = last[index]; i!=-1; i = e[i].nxt)
        if (!used[e[i].y]) {
          dfs(e[i].y);
          if (low[index]>low[e[i].y]) low[index] = low[e[i].y];
        }
        else if(!del[e[i].y]) if (low[index]>dfn[e[i].y])
          low[index] = dfn[e[i].y];
      if (low[index]==dfn[index]) {
        do {
          root[st[--top]] = index;
          del[st[top]] = 1;
        } while(st[top]!=index);
      }
    }
  3. Gabow
    算法的原理说起来太麻烦,还是直接给出代码。其实这个算法并不是很常用。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    #include <cstdio>
    #include <cstring>
    #include <stack>
      
    using namespace std;
      
    const int MAXN = 1000;
      
    struct node {
      int tar;
      node *nxt;
      node(int_tar,node *_nxt)
        : tar(_tar),nxt(_nxt) {}
    };
      
    node *g[MAXN];
    int n,m,co,all,dfn[MAXN],id[MAXN];
    stack<int> vec,path;
      
    void dfs(intx) {
      dfn[x] = co++;
      vec.push(x);
      path.push(x);
      for (node *i = g[x]; i!=NULL; i = i->nxt)
        if (dfn[i->tar]==-1) dfs(i->tar);
        else if(id[i->tar]==-1)
          while (dfn[path.top()]>dfn[i->tar]) path.pop();
      if (path.top()==x) path.pop();
      else return;
      for (inti; ;) {
        id[i = vec.top()] = all;
        vec.pop();
        if (i==x)break;
      }
      all++;
    }
      
    int main() {
      freopen("gabow.in","r",stdin);
      freopen("gabow.out","w",stdout);
      scanf("%d%d",&n,&m);
      memset(g,0,sizeof(g));
      for (inti = 0,a,b; i<m; i++) {
        scanf("%d%d",&a,&b); a--; b--;
        g[a] = newnode(b,g[a]);
      }
      co = all = 0;
      memset(dfn,-1,sizeof(dfn));
      memset(id,-1,sizeof(id));
      for (inti = 0; i<n; i++)
        if (dfn[i]==-1) dfs(i);
      for (inti = 0; i<n; i++)
        printf("%d\n",id[i]);
      return 0;
    }
转自: http://rchardx.is-programmer.com/posts/14898.html
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值