tarjan强连通分量缩点模板

本文解析了强连通图的概念,介绍了如何通过缩点简化问题,并详细讲解了Tarjan算法用于寻找有向图的强连通分量。通过DFS遍历和DFN、LOW数组,展示了如何判断和标记图中的强连通分量。适合算法竞赛和图论学习者阅读。

强连通定义

百度百科 强连通图(Strongly Connected Graph):是指在有向图G中,如果对于每一对vi、vj,vi≠vj,从vi到vj和从vj到vi都存在路径,则称G是强连通图。有向图中的极大强连通子图称做有向图的强连通分量。

缩点

在算法竞赛中,将强连通分量(例如环)处理成点会减少很多工作量。

在这里插入图片描述

上图中,1、3、5、2、6是一个强连通分量,经过缩点之后,变成下图。

在这里插入图片描述

tarjan算法

概念

从图中一点作为起点,进行DFS搜索遍历图,这样会得到一棵树,我们称之为DFS搜索树,该树中的每一条边都来自原图,我们将这些边称为树边,其他图中的边称为非树边

DFN数组:记录DFS序,也即时间戳、搜索顺序。
LOW数组:记录每个点经过一条非树边能回到的所有结点的最小DFN值注意,low值不同的点也可能属于一个强连通分量

求解

强连通图有如下性质,从图中一点出发,能经过所有点返回到起点。可以利用以上性质,结合深度优先搜索,如果某一时刻搜索到了一个被标记过的点,那么这一个回路上的点都属于同一个强连通分量。

代码实现

#include <iostream>
#include <stack>
#include <vector>
using namespace std;

const int N = 105;
int n, m;
vector<int> edge[N];
vector<int> belong[N]; //同一个强连通分量中的点
int dfn[N];            //dfs序
int root[N];           //强连通分量的根结点
int cnt;               //强连通分量数量
int ct;
stack<int> st;
bool instack[N];
void tarjan(int pos)
{
    dfn[pos] = ++cnt;
    root[pos] = cnt;
    st.emplace(pos);
    instack[pos] = 1;
    for (auto v : edge[pos])
    {
        if (!dfn[v])
        {
            tarjan(v);
            root[pos] = min(root[pos], root[v]);
        }
        else if (instack[v])
            /*
        到达一个标记过而且还在栈中的点,
        那么这一个回路都同属于一个强连通分量
        */
            root[pos] = min(root[pos], dfn[v]);
    }

    if (root[pos] == dfn[pos])
    {
        ++ct;
        int node;
        do
        {
            node = st.top();
            st.pop();
            instack[node] = 0;
            belong[ct].emplace_back(node);
        } while (root[node] != dfn[node]);
    }
}
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= m; ++i)
    {
        int u, v;
        cin >> u >> v;
        edge[u].push_back(v);
    }

    for (int i = 1; i <= n; i++)
        if (!dfn[i]) //图不一定连通
            tarjan(i);
    for (int i = 1; i <= n; i++)
        cout << i << ' ' << dfn[i] << endl;
    cout << ct << endl;
    for (int i = 1; i <= ct; i++)
        for (auto it : belong[i])
            cout << it << " belong to " << i << endl;
    return 0;
}
/*
7 9
1 3
5 1
1 6
2 5
6 2
3 4
3 5
5 6
2 7
*/

参考资料

  1. 图论——强连通分量(Tarjan算法)

  2. tarjan算法详解–关于图的连通性 & 连通分量

  3. 算法学习笔记(69): 强连通分量

### 强连通分量算法模板 强连通分量(Strongly Connected Component, SCC)是指在一个有向中,任意两个节都可以互相到达的最大子。通过 Tarjan 算法可以高效地找到这些强连通分量并将其成单个节,从而简化原结构。 以下是基于 Tarjan 算法的 C++ 实现代码模板: ```cpp #include <bits/stdc++.h> using namespace std; const int MAXN = 1e5 + 5; // 节数量上限 int n, m; vector<int> adj[MAXN]; // 邻接表 stack<int> stk; // 辅助栈 bool in_stack[MAXN]; // 判断当前节是否在栈中 int dfn[MAXN], low[MAXN]; // 时间戳数组和追溯值数组 int scc_id[MAXN]; // 储每个节所属的强连通分量编号 int idx = 0, cnt_scc = 0; // 当前时间戳计数器 和 强连通分量计数器 void tarjan(int u) { dfn[u] = low[u] = ++idx; // 初始化dfn和low stk.push(u); // 将u压入栈 in_stack[u] = true; // 标记u已在栈中 for (auto &v : adj[u]) { // 枚举所有邻接 if (!dfn[v]) { // 如果v未访问过 tarjan(v); low[u] = min(low[u], low[v]); // 更新low[u] } else if (in_stack[v]) { // 如果v已经在栈中 low[u] = min(low[u], dfn[v]); } } if (dfn[u] == low[u]) { // 找到一个新的强连通分量 ++cnt_scc; // 新增一个SCC编号 while (true) { // 提取该SCC中的所有节 int v = stk.top(); stk.pop(); in_stack[v] = false; scc_id[v] = cnt_scc; // 记录属于哪个SCC if (v == u) break; // 当弹出的是u时结束循环 } } } // 主函数调用部分 void solve() { cin >> n >> m; // 输入节数和边数 for (int i = 1; i <= m; ++i) { int u, v; cin >> u >> v; // 输入一条有向边 adj[u].push_back(v); } memset(dfn, 0, sizeof(dfn)); // 初始化dfn数组 memset(in_stack, 0, sizeof(in_stack)); idx = cnt_scc = 0; for (int i = 1; i <= n; ++i) { // 对每个节运行tarjan if (!dfn[i]) tarjan(i); } cout << "Total Strongly Connected Components: " << cnt_scc << "\n"; } ``` #### 关键说明 上述代码实现了 Tarjan 算法的核心逻辑[^2]。 - `dfn` 数组记录了每个节第一次被访问的时间戳。 - `low` 数组用于追踪能够回溯到的最早祖先节的时间戳。 - 使用辅助栈来保当前路径上的节,并判断哪些节已经形成一个完整的强连通分量。 - 每次当发现某个节满足条件 `dfn[u] == low[u]` 时,表示找到了一个新的强连通分量[^3]。 完成 Tarjan 算法后,可以通过构建新的后的来进行进一步处理。具体做法如下: - 创建新,其中每个节代表原始的一个强连通分量。 - 统计每条边连接的不同强连通分量之间的关系,忽略内部重复边。 --- ### 后的应用实例 假设我们需要计算拓扑序或者解决某些动态规划问题,在之后的新上操作会更加简单有效。例如,对于 DAG 上的经典 DP 或最短路问题,可以直接在此基础上展开分析。 ```cpp vector<vector<int>> new_adj(cnt_scc + 1); // 后的新 long long indegree[cnt_scc + 1] = {}; // 入度统计 for (int u = 1; u <= n; ++u) { for (auto &v : adj[u]) { if (scc_id[u] != scc_id[v]) { // 只考虑跨不同SCC的边 new_adj[scc_id[u]].push_back(scc_id[v]); indegree[scc_id[v]]++; } } } ``` 此段代码展示了如何根据原有生成后的结构[^1]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hesorchen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值