首页 > 编程语言 >强联通分量——Tarjan算法

强联通分量——Tarjan算法

时间:2024-09-26 09:36:08浏览次数:1  
标签:Tarjan 联通 int 算法 dfn low 节点 分量

Tarjan算法详解

参考文章:强连通分量

Tarjan算法是一种用于寻找有向图中强联通分量(Strongly Connected Components, SCCs)的算法。它是由Robert Tarjan在1972年提出的,基于深度优先搜索(DFS)和栈的数据结构。

基本概念

  • 强联通分量:在一个有向图中,如果一组节点中任意两个节点都可以互相到达,那么这组节点就构成了一个强联通分量。
  • DFS序:在深度优先搜索过程中,每个节点被访问的顺序。
  • low值:一个节点的low值表示该节点通过一条后向边或交叉边能够到达的最小DFS序的节点。

算法步骤

  1. 初始化

    • 对每个节点初始化dfn(DFS序)和low值。
    • 使用一个栈来记录当前路径上的节点。
    • 初始化一个计数器idx来记录DFS序。
  2. 深度优先搜索

    • 从任意一个未访问的节点开始进行DFS。
    • 访问到一个节点u时,设置dfn[u]low[u]为当前的idx值,并将u入栈。
    • 遍历u的所有邻接节点v
      • 如果v未被访问过,则递归访问v,并在递归返回后更新low[u]min(low[u], low[v])
      • 如果v在栈中,则更新low[u]min(low[u], dfn[v])
  3. 发现强联通分量

    • dfn[u]等于low[u]时,说明从u开始的路径形成了一个强联通分量。
    • 从栈中弹出节点,直到弹出u为止,这些节点构成一个强联通分量。

伪代码

function tarjan(u):
    dfn[u] = low[u] = ++idx
    stack.push(u)
    for each v in adj[u]:
        if v is not visited:
            tarjan(v)
            low[u] = min(low[u], low[v])
        else if v in stack:
            low[u] = min(low[u], dfn[v])
    if dfn[u] == low[u]:
        while true:
            v = stack.pop()
            // 处理强联通分量
            if v == u:
                break

题目:强连通分量

代码详解

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

const int N = 100010; // 定义常量N,表示图中节点的最大数量

vector<int> g[N]; // 邻接表存储图
int dfn[N], low[N], ins[N], idx; // 定义数组和变量
// dfn[u] 表示节点u的DFS序
// low[u] 表示节点u所在的子树中能跳到的最小DFN,且未被切掉,即ins[v]=true
// ins[u] 表示节点u是否在栈中
// idx 为当前遍历序列号
int bel[N], cnt; // bel[u] 表示节点u在哪个强联通分量里,cnt表示强联通分量的数量

int n, m; // n表示节点数,m表示边数
stack<int> stk; // 栈,用于存储当前路径上的节点
vector<vector<int>> ans; // 存储所有强联通分量

// Tarjan算法求强联通分量
void tarjan(int u) {
    dfn[u] = low[u] = ++idx; // 初始化dfn和low
    ins[u] = true; // 标记节点u在栈中
    stk.push(u); // 将节点u入栈

    // 遍历节点u的所有邻接节点
    for (auto v : g[u]) {
        if (!dfn[v]) { // 如果节点v未访问过
            tarjan(v); // 递归访问节点v
            low[u] = min(low[u], low[v]); // 更新low[u]
        } else if (ins[v]) { // 如果节点v在栈中
            low[u] = min(low[u], dfn[v]); // 更新low[u]
        }
    }

    // 如果dfn[u] == low[u],说明找到了一个强联通分量
    if (dfn[u] == low[u]) {
        cnt++; // 强联通分量数量加1
        vector<int> c; // 存储当前强联通分量的节点
        while (true) {
            int v = stk.top(); // 取出栈顶节点
            stk.pop(); // 弹出栈顶节点
            bel[v] = cnt; // 标记节点v属于当前强联通分量
            c.push_back(v); // 将节点v加入当前强联通分量
            ins[v] = false; // 标记节点v不在栈中
            if (v == u) break; // 如果栈顶节点是u,结束循环
        }
        sort(c.begin(), c.end()); // 对当前强联通分量的节点排序
        ans.push_back(c); // 将当前强联通分量加入结果
    }
}

signed main() {
    cin >> n >> m; // 输入节点数和边数
    for (int i = 1; i <= m; i++) { // 输入每条边
        int u, v;
        cin >> u >> v;
        g[u].push_back(v); // 添加边到邻接表
    }

    for (int i = 1; i <= n; i++) { // 对每个未访问的节点进行Tarjan算法
        if (!dfn[i]) tarjan(i);
    }

    cout << cnt << endl; // 输出强联通分量的数量
    sort(ans.begin(), ans.end()); // 对所有强联通分量排序
    for (auto c : ans) { // 输出每个强联通分量的节点
        for (auto i : c) {
            cout << i << " ";
        }
        cout << endl;
    }
}

简化版:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=100010;
vector<int> g[N];
int dfn[N],low[N],ins[N],idx;
int bel[N],cnt;
//ins[u]表示u这个结点是否在栈里面
//low[u]表示u这个结点所在的子树里面能跳到的DFN最小,且未被切掉,即ins[v]=true;
//dfn[u]表示u这个结点的DFS序
//idx为当前遍历序列号
//bel[u]表示u在哪个强联通分量里
int n,m;
stack<int> stk;
vector<vector<int> >ans;
void tarjan(int u)
{
	dfn[u]=low[u]=++idx;
	ins[u]=true;
	stk.push(u);
	for(auto v : g[u])
	{
		if(!dfn[v]) tarjan(v);
		if(ins[v]) low[u]=min(low[u],low[v]);
	}
	if(dfn[u]==low[u])
	{
		cnt++;
		vector<int> c;
		while(true){
			int v=stk.top();
			stk.pop();
			bel[v]=cnt;
			c.push_back(v);
			ins[v]=false;
			if(v==u) break;
		}
		sort(c.begin(),c.end());
	    ans.push_back(c);
	}
}
signed main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int u,v;
		cin>>u>>v;
		g[u].push_back(v);
	}
	for(int i=1;i<=n;i++)
	{
		if(!dfn[i]) tarjan(i);
	}
	cout<<cnt<<endl;
	sort(ans.begin(),ans.end());
	for(auto c : ans){
		for(auto i : c)
		{
			cout<<i<<" ";
		}
		cout<<endl;
	}

}

标签:Tarjan,联通,int,算法,dfn,low,节点,分量
From: https://www.cnblogs.com/Violetfan/p/18432785

相关文章

  • Tarjan算法缩点
    Tarjan算法缩点一.Tarjan算法缩点详解在图论中,缩点是指将有向图中的强联通分量(SCCs)缩成单个节点,从而得到一个更简单的图结构,称为缩点图或SCC图。Tarjan算法不仅可以用来寻找强联通分量,还可以用来进行缩点操作。基本概念强联通分量:在一个有向图中,如果一组节点中任意两个节点都......
  • 算法-复杂度分析
    复杂度分析不依赖具体的执行环境不用具体的测试数据在算法实现前,我们在脑海就可以评估算法的性能评估一个算法的性能:本质上就是评估这个算法代码执行的时间N为数据规模 大O复杂度表示法表示算法的性能,通常看最差的情况,算法运行的上界O(n) T=5n+2常数不重要,复杂......
  • 【算法】C++KMP算法的简洁实现
    目录简介next数组匹配完整代码简介对于朴素的字符串匹配算法,如果想在主串中寻找到第一次出现子串的位置,需要依次枚举主串中的每一个位置作为起始位置进行匹配尝试,如果主串中存在许多与子串相似结构的部分,那么朴素算法会进行大量的无用枚举,时间复杂度非常之高。KMP算法......
  • 【算法题】63. 不同路径 II-力扣(LeetCode)-”如果起点有障碍物,那么便到不了终点“
    【算法题】63.不同路径II-力扣(LeetCode)-”如果起点有障碍物,那么便到不了终点“1.题目下方是力扣官方题目的地址63.不同路径II一个机器人位于一个mxn网格的左上角(起始点在下图中标记为“Start”)。机器人每次只能向下或者向右移动一步。机器人试图达到网格......
  • 【算法】笔试题记录
    哇今天做了道特别有意思的题。编程就给了两道,第一题特别简单,a、b两个数,每次选其中一个数*2,这样操作两次,问最后得到的两数之和的期望值是多少。简单吧?因为每次选择都有两种可能性,操作两次后就会有四种可能的结果(22)。其中有两个结果是重复的(2a,2b),剩下两个分别是(a,4b)和(4a,......
  • 【鸟类识别系统】+计算机毕设项目+卷积神经网络算法+人工智能+深度学习+模型训练+Pyth
    一、介绍鸟类识别系统。本系统采用Python作为主要开发语言,通过使用加利福利亚大学开源的200种鸟类图像作为数据集。使用TensorFlow搭建ResNet50卷积神经网络算法模型,然后进行模型的迭代训练,得到一个识别精度较高的模型,然后在保存为本地的H5格式文件。在使用Django开发Web网页端操作......
  • 【动物识别系统】计算机毕设项目案例+Python卷积神经网络算法+模型训练+人工智能+深度
    一、介绍动物识别系统。本项目以Python作为主要编程语言,并基于TensorFlow搭建ResNet50卷积神经网络算法模型,通过收集4种常见的动物图像数据集(猫、狗、鸡、马)然后进行模型训练,得到一个识别精度较高的模型文件,然后保存为本地格式的H5格式文件。再基于Django开发Web网页端操作界面,实现......
  • 求最大公约数的三种算法
    #include<iostream>#include<vector>#include<algorithm>usingnamespacestd;intgcdByBruteForce(inta,intb){for(inti=min(a,b);i>0;--i){if(a%i==0&&b%i==0){returni;......
  • 矿山井下/传送带堆料检测AI算法的检测作用、工作原理及其解决方案
    传送带堆料分为两种情况,一种是传送带的井下堆料检测AI算法,一种是传送带上面的堆料检测AI算法,传送带井下堆料检测AI算法是在带式输送机的漏煤下方井下安装摄像仪,通过视频分析检测井下堆煤情况,当洒煤堆积到一定程度后,智慧矿山版ai盒子自动产生报警,并语音通知值班人员,也可通过前端音箱......
  • 代码中的大数定律:蒙特卡洛算法逼近圆周率π
    摘要:当程序员遇上π,蒙特卡洛算法成了他们的魔法棒。本文用一段C语言代码,将随机点的雨滴洒向数字的海洋,用概率的网捕捉π的踪迹。这不仅是一场算法的探险,更是对编程魔法的一次奇妙展示。认识蒙特卡洛算法蒙特卡洛算法是一类基于概率的算法的统称,不是特指某一种算法。它也被称为统计......