首页 > 编程语言 >Tarjan算法求强连通分量

Tarjan算法求强连通分量

时间:2022-11-18 10:45:04浏览次数:77  
标签:Tarjan min 连通 dfn low 节点 求强 分量

\(Tarjan\)——强连通分量

首先了解几个概念:强连通强连通图强连通分量

  • 强连通:在一个有向图\(G\)中,两个点\(a,b\),\(a\)可以走到\(b\),\(b\)可以走到\(a\),我们就说\((a,b)\)强连通

  • 强连通图:在一个有向图\(G\)中,任意两个点都是强连通

  • 强连通分量:在一个有向图\(G\)中,有一个子图,它任意两个点都是强连通,我们就说这个子图为强连通分量,特别的一个点也是一个强连通分量

如图所示:

显然可得:\(1,2,3,5\) 构成了一个强连通分量(一个点也是

首先引进一个概念:

  • 时间戳,用数组\(dfn\)表示,也就是搜索这个图的顺序,每个节点的时间戳不同

  • \(low[x]\),以\(x\)为根的子树中,每个节点中连接的点的时间戳的最小值
    \(low\)的初值:\(low[x]=dfn[x]\)
    可能有点难懂,但这个非常重要,是核心思想,等一下的模拟过程会详细讲述

  • 如何储存强连通分量呢,可以用

算法步骤

  • 每次遍历到一个新节点,就把它放进栈,如果这个点有出度,就继续往下找,直到不能再找
  • 每一次回来都要更新\(low\)值,当然是取小的那个,如果发现\(low[x]=dfn[x]\)那么它的子节点中肯定有一个连上来,既然可以过去又可以回来,很明显是一个强连通分量。那么这个\(x\)就是这个强连通分量的根节点,那么栈中间,比这个\(x\)晚进来的点就是\(x\)的子节点,那么这些点全部出栈,就组成了一个强连通分量

到这来就完了,但是好像还是 没有理解透彻(反正我是这样)

那么就模拟一下,还是这张图\(5->4\)

  • \(low[1]=dfn[1]=1\),\(1\)入栈

  • \(low[2]=dfn[2]=2\),\(2\)入栈

  • \(low[3]=dfn[3]=3\),\(3\)入栈

  • \(low[5]=dfn[5]=4\),\(5\)入栈

然后发现\(5\)连接着\(1\),已经\(1\)寻找过的了,那么就看看,\(PK\)下谁才是真正的祖先

\(low[1]=1\),\(low[5]=4\),好吧,\(5\)输了,所以\(1\)是\(5\)的根节点,

\[\large low[5]=min(low[5],low[1])=1 \]

继续发现还有\(4\),\(low[4]=dfn[4]=5\), \(4\)入栈

但是\(4\)已经没有了出度,往回退

发现\(low[4]=dfn[4]\)那么\(4\)就是一个强连通分量的根节点(其实也就它一个),\(4\)退栈

继续往回退:\(low[5]=min(low[5],low[4])=1\);

继续:一直到\(1\),

\[\large low[3]=min(low[3],low[5])=1 \\ low[2]=min(low[2],low[3])=1 \\ low[1]=min(low[1],low[2])=1\]

发现此时\(low[1]=dfn[1]\),所以\(1\)也是 一个强连通分量的根,此时发现栈里还有\(1,2,3,5\),所以这个强连通分量为\(1,2,3,5\)

\(1\)还有一个出度:\(4\)

寻找\(4\),\(low[4]=dfn[4]=5\),发现没有出度

\(low[4]=dfn[4]\),所以\(4\)是一个强连通分量的根节点(还是只有他一个),退栈

往回退,\(low[1]=min(low[1],dfn[4])=1\);

这样就完了吗?
万一还有图没有遍历到呢
所以要加一个语句:

 for (int i = 1; i <= n; i++)
     if (!dfn[i]) tarjan(i);

练习题

P2863 [USACO06JAN]The Cow Prom S
题目描述
有一个 \(n\) 个点,\(m\) 条边的有向图,请求出这个图点数大于 \(1\) 的强联通分量个数。

输入格式
第一行为两个整数 \(n\) 和 \(m\)。

第二行至 \(m+1\) 行,每一行有两个整数 \(a\) 和 \(b\),表示有一条从 \(a\) 到 \(b\) 的有向边。

输出格式
仅一行,表示点数大于 \(1\) 的强联通分量个数。

#include <bits/stdc++.h>
using namespace std;

const int N = 50002, M = N << 1;
int n, m, ans;

int stk[N], top;    // tarjan算法需要用到的堆栈
bool in_stk[N];     // 是否在栈内
int dfn[N];         // dfs遍历到u的时间
int low[N];         // 从u开始走所能遍历到的最小时间戳
int ts;             // 时间戳,dfs序的标识,记录谁先谁后
int id[N], scc_cnt; // 强连通分量块的最新索引号
int sz[N];          // sz[i]表示编号为i的强连通分量中原来点的个数

//链式前向星
int e[M], h[N], idx, w[M], ne[M];
void add(int a, int b, int c = 0) {
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

// tarjan算法求强连通分量
void tarjan(int u) {
    dfn[u] = low[u] = ++ts;
    stk[++top] = u;
    in_stk[u] = 1;
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (!dfn[j]) {
            tarjan(j);
            low[u] = min(low[u], low[j]);
        } else if (in_stk[j])
            low[u] = min(low[u], dfn[j]);
    }

    if (dfn[u] == low[u]) {
        ++scc_cnt; // 强连通分量的序号
        int x;     // 临时变量x,用于枚举栈中当前强连通分量中每个节点

        do {
            x = stk[top--];    //弹出节点
            in_stk[x] = false; //标识不在栈中了
            id[x] = scc_cnt;   //记录每个节点在哪个强连通分量中
            sz[scc_cnt]++;     //这个强连通分量中节点的个数+1
        } while (x != u);
    }
}

int main() {
    memset(h, -1, sizeof h);
    scanf("%d %d", &n, &m);

    for (int i = 1; i <= m; i++) {
        int a, b;
        scanf("%d %d", &a, &b);
        add(a, b);
    }

    for (int i = 1; i <= n; i++)
        if (!dfn[i]) tarjan(i);

    //枚举结果数组,统计题目要求的 点数大于 1 的强联通分量个数
    for (int i = 1; i <= scc_cnt; i++)
        if (sz[i] > 1) ans++;

    printf("%d\n", ans);
    return 0;
}

标签:Tarjan,min,连通,dfn,low,节点,求强,分量
From: https://www.cnblogs.com/littlehb/p/16902431.html

相关文章

  • P8436 【模板】边双连通分量
    P8436【模板】边双连通分量//这个是看边和点,而不是看点和点#include<bits/stdc++.h>usingnamespacestd;constintN=5e5+5;constintM=2e6+5;inth[N],ne[M<<1......
  • P8435 【模板】点双连通分量
    P8435【模板】点双连通分量#include<bits/stdc++.h>usingnamespacestd;constintN=5e5+5;constintM=4e6+6;inth[N],ne[M],e[M],tot;voidadd(intfrom,int......
  • LCA 之 Tarjan(离线)算法
    \(LCA\)之\(Tarjan\)(离线)算法什么是最近公共祖先?在一棵没有环的树上,每个节点肯定有其父亲节点和祖先节点,而最近公共祖先,就是两个节点在这棵树上深度最大的公共的祖先......
  • Tarjan算法
    tarjan求无向图割点,若x是根节点,则子树个数>1时x时割点;若x是非根节点,当ipt[x]<=low[y]时x是割点,说明y的子树无法通过非父子边回溯到x的祖先,那么删掉x,图将分裂成两个字图,即x......
  • 学习笔记——双连通分量
    前言我们的神,MC曾经曰过,Tarjan是\(11\)级算法。边双桥:在一张连通无向图中,如果去掉一条边使得图的极大连通分量增加了,那么这条边就叫做桥。边双连通分量:一张无......
  • P2272 [ZJOI2007]最大半连通子图
    原题链接题目的意思就是说找到最长路径的长度及数量。显然,我们首先要tarjan缩点,然后建立新图,但要注意的是不能有重边,因为会影响我们计算数量,那我们可以用map记录一下两点......
  • 强连通图|连通分量
    求无向图的连通分量或有向图的强连通分量—tarjan()ccf高速公路23计算机考研—强连通分量的个数怎么求?......
  • P3379 【模板】最近公共祖先(LCA)tarjan算法
    tarjan算法求LCA//tarjan算法#include<bits/stdc++.h>usingnamespacestd;constintmaxn=5e5+10;vector<int>tre[maxn];structnode{ intto; intid;};vect......
  • 单连通域和多(复)连通域
    单连通域定义:一个连通域B内任意画一条闭合曲线,闭合域内一定属于连通域B 假如闭合域内存在区域不属于连通域B,则为多连通域。大白话1:连通域内不能有洞大白话......
  • 【模板】Tarjan
    postedon2022-07-0720:52:49|under模板|source0x00有向图缩点现有一有向图\(G=(V,E)\),称一个点集\(E'\inE\)为强连通分量,当且仅当\(E'\)的任意两点可以互......