Tarjan算法

强连通图:在有向图G中,如果任意两个点都是联通的(这两个点可以互相到达),那么这个图就是强连通图。

极大强连通子图:不能再扩大的强连通子图(已经成为局部最大的)。

强连通分量:在非强连通图中,其极大强连通子图可理解为该图的强连通分量。


Tarjan算法

该算法的基础:基于DFS算法,同时需要两个数组dfn和low来辅助。
dfn数组:dfn[v]表示搜索到v点的步数。
low数组:low[v]表示该点所能追溯到栈内最早的出现的那个点。

操作原理:强连通分量由好多个环组成,当有环形成时,这一条路径上的low值是统一的,即这一条路径上的所有点都属于同一个强连通分量。

算法规则:
首次搜索到u点的时候,low[u]和dfn[u]的值都为到达这个点的步数(时间)。
每次遇到一个没有被标记过的点就把这个点放入到栈中。
当点u遍历到其中一个点v的时候,如果这个点v不在栈中,low[u] = min(low[u], low[v]);如果这个点v在栈中,low[u] = min(low[u], dfn[v])。
当搜索到一个点的low和dfn相等时,栈内的这些点就构成了一个强连通分量。

算法演示:(https://www.byvoid.com/zhs/blog/scc-tarjan

从节点1开始进行DFS,这样把1 3 5 6这四个节点都放入到了栈中,到了6的时候DFN和LOW都是4,那么{6}本身就构成了第一个强连通分量,6从栈中退出,返回至节点5。(如下图)
这里写图片描述

节点5的DFN和LOW值也是相同的,{5}本身也构成了第二个强连通分量,5从栈中退出,返回节点3。(如下图)
这里写图片描述

节点3遍历到了节点4,节点4有两条边分别到了6和1,到了6的时候6已经成为了单独的强连通分量,可以用vis[6]=2标记将其跳过,然后4找到了节点1,发现节点1在栈中,因此low[4] = min(low[4], dfn[1]) = 1,然后返回到了节点3,此时节点3的low[3] = min(low[3], low[4]) = 1。
这里写图片描述

然后返回到节点1,节点1遍历到了节点2,节点2右找到了节点4,节点4在栈中,因此low[2] = min(low[2], dfn[4]) = 1,然后返回到节点1,此时发现回到的元素dfs == low值,栈中和元素构成第三个强连通分量,算法结束。
这里写图片描述


代码模板如下:

#include <cstdio>
#include <cstring>
#include <iostream>

using namespace std;

//链式前向星存图
struct Edge {
    int v;      //边的终点
    int nex;    //下一条边
} edge[10005];

int edgeNum;    //边的条数
int id[105];
int vis[105];
int n, m;

int componentMap[105];  //map[i]表示的是第i个点属于的强连通分量值
int componentNum;   //强连通分量的个数
int dfn[105];
int low[105];

int stack[105];
int stacksize;

void dfs(int u, int step) {
    dfn[u] = step;
    low[u] = step;
    vis[u] = 1;
    stack[++stacksize] = u;
    for (int i = id[u]; i != -1; i = edge[i].nex) {
        int v = edge[i].v;
        if (vis[v] == 0)
            dfs(v, step + 1);
        if (vis[v] == 1)
            low[u] = min(low[u], low[v]);
    }
    if (dfn[u] == low[u]) {
        componentNum++;
        int k;
        do {
            k = stack[stacksize--];
            componentMap[k] = componentNum;
            vis[k] = 2;
        } while (k != u);
    }
}

void tarjan() {
    componentNum = 0;
    stacksize = 0;
    memset(vis, 0, sizeof(vis));
    for (int u = 1; u <= n; u++) {
        if (vis[u] == 0)
            dfs(u, 1);
    }
}

void add (int bgn, int nd) {
    edge[edgeNum].nex = id[bgn];
    edge[edgeNum].v = nd;
    id[bgn] = edgeNum++;
}

void init () {
    edgeNum = 1;
    for (int i = 0; i <= n; i++) {
        id[i] = -1;
    }
    memset(edge, 0, sizeof(edge));
}

int main () {
    scanf("%d %d", &n, &m);
    init();
    for (int i = 0; i < m; i++) {
        int a, b;
        scanf("%d %d", &a, &b);
        add(a, b);
    }
    tarjan();

    for (int i = 1; i <= n; i++)
    cout << i << " : " << componentMap[i] << endl;

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值