当前位置: 首页 > news >正文

Tarjan 算法的两种用法

Tarjan 算法:

利用 DFS 过程中记录的时间戳和追溯值来发现图的特定结构 

  1. 时间戳(dfn [u])
    • 表示节点u在 DFS 中被首次访问的顺序(访问顺序编号)。
  2. 追溯值(low [u])
    • 表示节点u或其子孙能通过非父子边追溯到的最早时间戳的节点,计算方式:
      • low[u] = dfn[u](初始值)
      • 遍历所有邻接节点v
        • v是父节点,跳过;
        • v未访问,递归访问后更新low[u] = min(low[u], low[v])
        • v已访问且在当前栈中,更新low[u] = min(low[u], dfn[v])
  3. 栈结构
    • 用于记录当前 DFS 路径上的节点,判断强连通分量或割点等结构。

 

Tarjan 求强连通分量

  1. 强连通节点对
    对于有向图中的两个节点 u 和 v,若存在从 u 到 v 的有向路径,同时也存在从 v 到 u 的有向路径,则称 u 和 v 是强连通的。

    • 例:在有向环 A→B→C→A 中,任意两点间都有双向路径,因此 、、 两两强连通。
  2. 强连通图
    若有向图中任意两个节点都强连通,则称该图为强连通图

    • 例:单个有向环是典型的强连通图;若图中存在多个环且环间无连接,则不是强连通图。
  3. 强连通分量(Strongly Connected Component, SCC)
    有向图中的极大强连通子图,即:

    • 该子图内任意两点强连通;
    • 不存在更大的子图包含它且满足强连通条件。

算法原理
Tarjan 算法基于对图进行 DFS,视每个连通分量为搜索树中的一棵子树,在搜索过程中维护一个栈,每次把搜索树中尚未处理的节点加入栈中。

#include<bits/stdc++.h>
using namespace std; 
int dfn[N], low[N], dfncnt, s[N], in_stack[N], tp; // dfn为时间戳,low为子树最小时间戳,dfncnt为时间戳计数器,s为栈,in_stack标记是否在栈中,tp为栈顶指针
int scc[N], sc; // scc记录每个节点所属强连通分量编号,sc为强连通分量计数器
int sz[N]; // 记录每个强连通分量的大小void tarjan(int u) { low[u] = dfn[u] = ++dfncnt; // 初始化时间戳和子树最小时间戳s[++tp] = u; // 将节点入栈in_stack[u] = 1; // 标记节点在栈中for (int i = h[u]; i; i = e[i].nex) { // 遍历u的所有邻接边const int &v = e[i].t; // 邻接节点vif (!dfn[v]) { // 如果v未被访问tarjan(v); // 递归访问vlow[u] = min(low[u], low[v]); // 更新u的子树最小时间戳} else if (in_stack[v]) { // 如果v在栈中(属于当前强连通分量)low[u] = min(low[u], dfn[v]); // 更新u的子树最小时间戳}}// 当u是强连通分量的根节点时if (dfn[u] == low[u]) { ++sc; // 强连通分量计数器加1do { scc[s[tp]] = sc; // 记录节点所属强连通分量sz[sc]++; // 强连通分量大小加1in_stack[s[tp]] = 0; // 标记节点出栈} while (s[tp--] != u); // 弹出栈中节点直到遇到u}
}

 

Tarjan 算法来求解无向图中的所有桥(割边)。

桥是指在一个无向图中,如果删除某条边后,图的连通分量数目增加,则称该边为桥。 

  • 核心思想:利用 DFS 遍历,通过记录节点的时间戳(dfn)和 “追溯值”(low)判断割点。
  • 关键条件(设 u 为当前节点,v 为 u 的子节点):
    • 情况 1:若 u 是 DFS 树的根节点,且 u 有至少两个子节点(删除 u 后子树分裂),则 u 是割点;
    • 情况 2:若 u 不是根节点,且存在子节点 v 满足 low[v]≥dfn[u],则 u 是割点(说明 v 的子树无法通过非父边回到 u 的祖先,删除 u 后 v 的子树与其他部分断开)。
  • 算法步骤
    1. 初始化 dfn 和 low 数组,时间戳从 1 开始;
    2. 对每个未访问节点启动 DFS,遍历过程中更新 low[u] 为 dfn[u] 与所有邻接节点 low 的最小值;
    3. 对每个子节点 v,若 v 是父节点则跳过,否则递归处理 v,并更新 low[u]=min(low[u],low[v]);
    4. 回溯时检查上述割点条件。

 

#include <bits/stdc++.h>
using namespace std;
int maps[151][151];           // 邻接矩阵存储图结构
struct Edge {                 // 边的结构体,用于存储桥int x,y;
} E[5001];
int dfn[151],low[151],n,m,id,cnt,f[151];
// dfn[]: 时间戳数组,表示节点被访问的顺序
// low[]: 追溯值数组,表示从当前节点出发,能够追溯到的最早时间戳
// n: 节点数,m: 边数,id: 时间戳计数器,cnt: 桥的数量
// f[]: 存储每个节点的父节点// 边的比较函数,用于排序
bool cmp(struct Edge a,struct Edge b) {if(a.x==b.x)return a.y<b.y;return a.x<b.x;
}// 添加桥到结果数组
void addEdge(int x,int y){E[++cnt].x=x;E[cnt].y=y;
}// Tarjan算法核心函数,寻找桥
void tarjan(int x){int c=0,y;dfn[x]=low[x]=++id;       // 初始化时间戳和追溯值for(register int i=1; i<=n; i++) {  // 遍历所有节点if(!maps[x][i])continue;  // 如果节点i与x之间没有边,跳过y=i;// 如果节点i已经被访问过且不是当前节点的父节点if(dfn[y]&&y!=f[x])low[x]=min(low[x],dfn[y]);// 如果节点i未被访问过if(!dfn[y]) {f[y]=x;           // 设置父节点tarjan(y);        // 递归处理子节点low[x]=min(low[x],low[y]);  // 更新追溯值// 如果子节点的追溯值大于当前节点的时间戳,则(x,y)是桥if(low[y]>dfn[x])addEdge(x,y);}}
}int main() {int x,y;cin>>n>>m;                // 输入节点数和边数// 读入每条边,构建邻接矩阵for(register int i=1; i<=m; i++) {cin>>x>>y;maps[x][y]=maps[y][x]=1;  // 无向图,双向标记}// 对每个未访问的节点调用Tarjan算法for(register int i=1; i<=n; i++) {if(!dfn[i])tarjan(i);}// 对找到的桥进行排序sort(E+1,E+cnt+1,cmp);// 输出每条桥,按节点编号从小到大的顺序for(register int i=1; i<=cnt; i++) {cout<<min(E[i].x,E[i].y)<<' '<<max(E[i].x,E[i].y)<<endl;}return 0;
}

 

http://www.dtcms.com/a/265665.html

相关文章:

  • 支持向量机(SVM)分类
  • JavaScript的现代进阶:从ES6到ES15
  • 机器学习-03(机器学习任务攻略)
  • npm 命令入门指南(前端小白版)
  • 使用numpy的快速傅里叶变换的一些问题
  • 记忆翻牌记忆力小游戏流量主微信小程序开源
  • 万能公式基分析重构补丁复分析和欧拉公式原理推导
  • 国外开源集成平台(业务编排)camel及Mule介绍
  • 为什么是直接在**原型(prototype)上**添加函数
  • 构建引擎: 打造小程序编译器
  • 边缘计算解决方案:电力作业行为图像识别
  • Mac电脑 触摸板增强工具 BetterTouchTool
  • Linux开发工具——gcc/g++
  • 虚拟机网络检查
  • 数据结构-栈的实现
  • 电动车信用免押小程序免押租赁小程序php方案
  • 数据库运维手册指导书
  • 移动端Html5播放器按钮变小的问题解决方法
  • Laravel8中使用phpword生成word文档
  • LeetCode--40.组合总和II
  • 【ArcGIS Pro】属性表咋不能编辑了?
  • wvp-GB28181-pro 项目 ZLMediaKit 部署 (Centos7)
  • XILINX Ultrascale+ Kintex系列FPGA的架构
  • R语言开发记录,二(创建R包)
  • vue-37(模拟依赖项进行隔离测试)
  • 《导引系统原理》-西北工业大学-周军-“2️⃣导引头的角度稳定系统”
  • 定时点击二次鼠标 定时点击鼠标
  • Node.js中exports与module.exports区别
  • DPDK开发环境配置
  • SpringCloud系列(49)--SpringCloud Stream消息驱动之实现生产者