算法描述

tarjan算法思想:从一个点开始,进行深度优先遍历,同时记录到达该点的时间(dfn记录到达i点的时间),和该点能直接或间接到达的点中的最早的时间(low[i]记录这个值,其中low的初始值等于dfn)。如图:

Tarjan算法求有向图的强连通分量

  假设我们从1开始DFS,那么到达1的时间为1,到达2的时间为2,到达3的时间为3。同时,点1能直接或间接到达的点中,最小时间为1,点2能通过3间接到达点1,所以点2可到达最早的点时间为1,点3可以直接到达点1,故点3到达的最早的点的时间为1。)。对于每一个没有被遍历到的点A,如果从当前点有一条到未遍历点A的有向边,则遍历到A,同时将点A入栈,时间戳+1并用dfn[a]记录到达点A的时间,枚举从A发出的每一条边,如果该边指向的点没有被访问过,那么继续dfs,回溯后low[a]=min(low[a],low[j])(其中j为A可以到达的点。)如果该点已经访问过并且该点仍在栈里,那么low[a]=min(low[a],dfn[j])。

解释:

  若点j没有被访问过,那么回溯后low[j]就是j能到达最早点,a能到达的最早点当然就是a本身能到达的最早点,或者a通过j间接到达的最早点。若点j已经被访问过,那么low[j]必然没有被回溯所更新。所以low[a]就等于a目前能到达的最小点或a直接到达点j时点j的访问时间。注意:两个句子中的“或”其实指的是两者的最小值。

那么如果我们回溯到一个点K他的low[k]=dfn[k]那么我们将K及其以前在栈中的点依次弹出,这些点即为一个强连通分量。(说明从k出发又回到k)

证明:

  因为该点dfn=low,所以在栈中的该点以上的点都能由该点直接或间接的到达。同时栈中在该点前的任意一点j,其dfn[j] != low[j](否则点j比点k靠前,又因为dfn[j]=low[j],j一定先被弹出了。)那么这个点j通过low[j]这个时间的点,一定能到达点k,否则,low[j]能到达点i,又因为dfn>=low所以有2种情况1、dfn>low:那么我们可以找到前面一个更小的点。2、dfn=low:应该在回溯到i的时候就找到了一个强连通分量,从而出栈了。而点k前的点没有出栈,证明其中任意一点都能直接或者间接到达点k,进而证明这些点可以两两互达。

先用简单模板刷一道水题入门:

迷宫城堡

本题的边不带权值,可以用vector表示邻接链表:

#include <cstdio>
#include <memory.h>
#include <vector>
#include <algorithm>

using namespace std;

//本题的顶点号从1到n,故可直接用作vector的下标,不需要用head数组离散化 

const int MAXN = 10001;

int top;
int Stack[MAXN];
bool inStack[MAXN];
//DFN(u)为节点u搜索的次序编号(时间戳),Low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号  
int dfn[MAXN], low[MAXN];
int Bcnt, Dindex; //记录强连通的个数和当前时间 
vector<int> ve[MAXN]; //邻接表保存边

void init() {
    Bcnt = Dindex = top = 0;
    memset(dfn, -1, sizeof dfn);
    memset(inStack, false, sizeof inStack);
    for (int i = 1; i < MAXN; ++i) ve[i].clear();
}

void tarjan(int u) {
    int v = 0;
    dfn[u] = low[u] = ++Dindex;
    inStack[u] = true;
    Stack[++top] = u;
    int t = ve[u].size();
    for (int i = 0; i < t; ++i) {
        v = ve[u][i];
        if (dfn[v] == -1) {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        } else if (inStack[v])
            low[u] = min(low[u], dfn[v]);
    }
    if (dfn[u] == low[u]) {
        Bcnt++;
        do {
            v = Stack[top--];
            inStack[v] = false;
        } while (u != v);
    }
} 

int main() {
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
#endif
    int n, m;
    while (scanf("%d%d", &n, &m) != EOF) {
        if (m == 0 && n == 0) break;
        init();
        while (m--) {
            int a, b;
            scanf("%d%d", &a, &b);
            ve[a].push_back(b); 
        }
        for (int i = 1; i <= n; ++i) 
            if (dfn[i] == -1) tarjan(i);
        if (Bcnt == 1) puts("Yes");
        else puts("No");
    } 

    return 0;
}
View Code

相关文章:

  • 2021-08-21
  • 2021-11-04
  • 2022-12-23
  • 2021-08-18
  • 2022-02-09
  • 2021-10-08
猜你喜欢
  • 2022-02-22
  • 2022-12-23
  • 2022-12-23
  • 2022-01-09
  • 2022-02-09
  • 2022-02-09
  • 2022-02-09
相关资源
相似解决方案