tarjan算法模板及其代码解释

首先解释一下三个概念

强连通(strongly connected): 在一个有向图G里,设两个点 a b 发现,由a有一条路可以走到b,由b又有一条路可以走到a,我们就叫这两个顶点(a,b)强连通。


强连通图: 如果 在一个有向图G中,每两个点都强连通,我们就叫这个图,强连通图。


强连通分量strongly connected components):在一个有向图G中,有一个子图,这个子图每2个点都满足强连通,我们就叫这个子图叫做 强连通分量 

我们先来看一段简要的伪代码

tarjan(u){

  DFN[u]=Low[u]=++Index // 为节点u设定次序编号和Low初值

  Stack.push(u)   // 将节点u压入栈中

  for each (u, v) in E // 枚举每一条边

    if (v is not visted) // 如果节点v未被访问过

        tarjan(v) // 继续向下找

        Low[u] = min(Low[u], Low[v])

    else if (v in S) // 如果节点u还在栈内

        Low[u] = min(Low[u], DFN[v])

  if (DFN[u] == Low[u]) // 如果节点u是强连通分量的根

  repeat v = S.pop  // 将v退栈,为该强连通分量中一个顶点

  print v

  until (u== v)

}

再来一张网上很多人用过的图来做解释

从1进入 DFN[1]=LOW[1]= ++index ----1
入栈 1
由1进入2 DFN[2]=LOW[2]= ++index ----2
入栈 1 2
之后由2进入3 DFN[3]=LOW[3]= ++index ----3
入栈 1 2 3
之后由3进入 6 DFN[6]=LOW[6]=++index ----4
入栈 1 2 3 6

到达6之后发现6没有出度,递归完毕,同时发现DFN【6】 == LOW【6】,出栈,然后返回上一级,上一级中更新LOW【3】 = min(LOW【3】, LOW【6】),发现没有变,然后判断出DFN【3】 == LOW【3】,出栈,返回上一级,然后在结点2发现有出度,走结点5这一条路,结点5有俩条出度,6已经被访问过了,不再访问,1也被访问过了,但是1还在栈中,于是我们更新LOW【5】 = min(LOW[5】, LOW【1】) = 1,这时候判断LOW【5】 != DFN【5】,所以5不用出栈,直接递归完毕返回上一级结点2,结点2更新LOW【2】 = min(LOW[2], LOW[5]) = 1,同样LOW【2】 != DFN【2】, 2也不用出栈,返回上一级结点1,1有一条没有访问的出度,所以我们访问4,然后在节点4发现5已经访问过但是还在栈中,于是LOW[4] = min(LOW[4], DFN[5]) = 5,判断出DFN[4] != LOW【4】,所以不用出栈,然后返回上一级结点1,更新LOW【1】 = min(LOW[1], LOW[4]), 没有变化,但是发现DFN[1] == LOW[1],然后栈里面1以及1以上的结点全部出栈,tarjan算法模拟完毕

贴一段targin算法,算法的输入以及输出就是上面模拟的例子

input:

6 8

1 3

1 2

2 4

3 4

3 5

4 6

4 1

5 6

output:

6

5

3 4 2 1

#include<iostream>
using namespace std;
const int maxn = 10010;
int head[maxn], DFN[maxn], LOW[maxn], Stack[maxn], vis[maxn];
int cnt = 1, n, m, a, b;

//链式前向星,不知道的可以百度去学一下
struct Node{
    int to;
    int w;
    int next;
}edge[2 * maxn];

void add(int u, int v, int w) {
    edge[cnt].to = v;
    edge[cnt].w = w;
    edge[cnt].next = head[u];
    head[u] = cnt++;
}

int total, index;
void tarjan(int u) {//代表第几个点在处理。递归的是点。
    DFN[u] = LOW[u] = ++total;// 新进点的初始化。
    Stack[++index] = u;//入栈
    vis[u] = 1;//表示在栈里面
    for (int i = head[u]; i != 0; i = edge[i].next) {
        int v = edge[i].to;
        if (!DFN[v]) {//如果没有访问过
            tarjan(v);//递归访问
            LOW[u] = min(LOW[u], LOW[v]);//找最小父根
        } else if (vis[v]) {//如果访问过,并且还在栈里面
            LOW[u] = min(LOW[u], DFN[v]);//找最小父根
        }
    }
    if (DFN[u] == LOW[u]) {//发现是父根
        do{
            cout << Stack[index] << " ";
            vis[Stack[index]] = 0;
            index--;
        } while (Stack[index + 1] != u);//边出栈边输出
        cout << endl;
    }
}

int main() {
    cin >> n>> m;
    for (int i = 0; i < m; i++) {
        cin >> a >> b;
        add(a, b, 0);
    }
    for (int i = 1; i <= n; i++) {
        if (!DFN[i]) {
            tarjan(i);//当这个点没有访问过,就从此点开始。防止图没走完
        }
    }
    return 0;
}

整个tarjan算法的流程大致就是这样,可能写的有点简陋,不过要是直接看懂伪代码之后再去理解文字再结合C++代码应该比较好理解,完毕。

猜你喜欢

转载自blog.csdn.net/LanQiLi/article/details/84976596