SCC-Tarjan算法,强连通分量算法,从dfs到Tarjan详解

news2024/11/25 16:33:43

文章目录

    • 前言
    • 定义
      • 强连通
      • 强连通分量
    • Tarjan算法原理及实现
      • 概念引入
        • 搜索树
        • 有向边的分类
        • 强连通分量的根
        • 时间戳
        • 追溯值
      • 算法原理
        • 从深搜到Tarjan
        • Tarjan算法流程
        • Tarjan算法代码实现
    • OJ练习:

前言

强连通分量是图论中的一个重要概念,它在许多领域都有广泛的应用,如网络路由中识别环路,社交网络分析,编译器优化识别出代码中的循环结构,图像处理中识别出图像中的连通区域,从而进行图像分割和特征提取等。因而了解强连通分量的概念以及其求解算法是十分重要的。我们本文介绍Tarjan算法,Tarjan算法在双连通分量求解中也有应用,我们后续博客中也会介绍。


定义

强连通

若一张有向图的节点两两互相可达,则称这张图是**强连通(SC,strongly connected)**的。

强连通分量

强连通分量(Strongly Connected Components, SCC): 极大的强连通子图。
如下图中G1 = {1,2,3,4,9} 和 G2 = {5,6,8} 以及 G3 = {7}就是三个强连通分量。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Tarjan算法原理及实现

概念引入

搜索树

我们dfs对图遍历,保证每个点只访问一次,访问过的节点和边构成一棵有向树,我们称之为搜索树

如我们上图从1开始深搜遍历就会得到如下搜索树。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

有向边的分类

深搜对于有向边的访问分为四类:

1.树边(tree edge):访问节点走过的边。图中的黑色边。
2.返祖边(back edge):指向祖先节点的边。图中的红色边。
3.横叉边(cross edge):右子树指向左子树的边。图中的绿色边。.
4.前向边(forward edge):指向子树中节点的边。图中的蓝色边。

我们不难发现返祖边必和树边构成环。横叉边可能和返祖边构成环。

前向边无用,因为前向边如果作为某个环中的边,必有一个更大的环替代该环。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

强连通分量的根

如果节点x是某个强连通分量在搜索树中遇到的第一个节点,那么这个强连通分量的其余节点肯定是在搜索树中以x为根的子树中。节点x被称为这个强连通分量的根

时间戳

我们用数组dfn[]来保存节点第一次访问时间,dfn[x]即节点x第一次访问的时间戳

追溯值

数组low[]来记录每个节点出发能够访问的最早时间戳,记low[x]节点x出发能够访问的最早时间戳,即追溯值

算法原理

从深搜到Tarjan

Tarjan算法通过记录深搜遍历中每个节点的第一次访问时间来找到强连通分量的根以及其余节点。

前面已经讲了Tarjan是基于深搜的,所以我们的Tarjan实际上是在深搜遍历图的模板上加以修改而实现的

我们先给出链式前向星存图的深搜代码。(关于链式前向星,详见:一种实用的边的存储结构–链式前向星-CSDN博客)

void tarjan(int x)//深搜模板
{
    //链式前向星存图
    for (int j = head[x]; ~j; j = edges[j].nxt)
    {
    	int v = edges[j].v;
    	if(vis[v]) continue;
    	vis[v] = 1;
    	tarjan(v);
    }
}

即然Tarjan算法是跟时间戳和追溯值有关的,我们如何在深搜过程中完成对二者的记录呢?

我们用全局变量tot来记录时间,由于上面深搜代码保证了进入函数的都是未访问即第一次访问的节点,所以我们进入函数体的时候记录dfn[x]为++tot,并初始化low[x]为dfn[x],则有如下代码

void tarjan(int x)//深搜模板
{
    dfn[x] = low[x] = ++tot;
    for (int j = head[x]; ~j; j = edges[j].nxt)
    {
    	int v = edges[j].v;
    	if(vis[v]) continue;
    	vis[v] = 1;
    	tarjan(v);
    }
}

对于dfn的值是无可指摘的,但是对于非强连通分量根节点x的low值显然不是dfn[x],那么我们如何在遍历过程中去维护low呢?

如果相邻节点v未访问,那么我们先对v深搜,然后用v的low值更新low[x]

如果已经访问过了,直接用v的low值更新low[x],则有

void tarjan(int x)//深搜模板
{
    dfn[x] = low[x] = ++tot;
    for (int j = head[x]; ~j; j = edges[j].nxt)
    {
        int v = edges[j].v;
        if (!dfn[v])//预先初始化的dfn全为0,时间戳为0说明还未访问
        {
            tarjan(v);
            low[x] = min(low[x], low[v]);
        }
        else
        {
            low[x] = min(low[x], low[v]);
        }
    }
}

我们现在似乎只是完成了深搜遍历图以及记录dfn和low罢了,还没有真正的涉及到强连通分量的求解。

对于一个强连通分量的根来讲,当它的访问完毕后,它所在的强连通分量所有节点必然都已经访问过,也就是说我们需要从根的时间戳往后的访问节点中找到其强连通分量内的其他点。

这就要求我们用一个数据结构来存储访问过的点,这里我们选择使用,因为这样能保证先访问的在栈底,强连通分量中后访问的节点在栈中位置都在根节点上方,而我们如何判断根呢?我们知道根是所在强连通分量第一个访问的点,所以一定满足dfn[root] == low[root]

我们回看最初的图和搜索树

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们在深搜的过程中标记时间戳的同时将节点入栈,那么我们按照搜索树往下走:

  • 发现最先访问完的节点是3,此时栈中元素为{1,2,3},我们结合原图来看发现1,2,3在一个强连通分量内,但是由于3不是根,不能保证所在强连通分量遍历完了,所以不做操作
  • 接下来2遍历完了,和3一样,不做操作。
  • 回到1后,接着从4往下遍历。到7的时候,7又成为了访问完的节点,我们发现dfn[7] == low[7] = 7,所以7是其所在强连通分量的根,其所在强连通分量其它点都在栈中,我们发现7在栈顶,所以7所在的强连通分量只有7,到此我们将7弹栈
  • 接着遍历,此时到8的时候又发现8也访问完了,由于8不是根,所以不操作,这样一直下去回到了5,我们发现dfn[5] == low[5] = 5,所以5是其所在强连通分量的根,其所在强连通分量其它点都在栈中,我们此时可以发现栈中5之上的正好是其所在强连通分量内的其他点,因为之前我们已经将7弹栈了,这样我们又处理了5所在强连通分量{5,6,8}
  • 接着往下走,我们仿照先前的步骤,又得到了强连通分量{1,2,3,4,9}
  • 至此所有强连通分量已经得到:{1,2,3,4,9},{5,6,8},{7}

所以我们可以发现,时间戳dfn和追溯值low可以帮我们判定强连通分量的根,从而从栈中拿出其所在强连通分量的节点,且先访问完的强连通分量已经弹栈,不会对后访问完的强连通分量造成影响(更严格的证明可以用反证法自己试着证明一下)。

而且我们也发现一点,由于low值只跟自己强连通分量内点的low值有关,所以我们上面深搜代码中维护low值的部分第二个分支判断可改为在栈中,因为如果不在栈中而且访问过了说明它所在的强连通分量已经解决了,跟当前点无关。如下:

        if (!dfn[v])//预先初始化的dfn全为0,时间戳为0说明还未访问
        {
            tarjan(v);
            low[x] = min(low[x], low[v]);
        }
        else if(instk[v]
        {
            low[x] = min(low[x], low[v]);
        }
Tarjan算法流程

我们到这里就可以得出Tarjan的算法流程:

  1. 访问x时,给时间戳、初始化low、入栈。枚举x的邻点v,分三种情况。
  2. 若v尚未访问:对v深搜。回x时,用low[v]更新low[x]。因为x是v的父节点,v能访问到的点,x也一定也能访问到
  3. 若v已访问且在栈中:说明y是祖先节点或左子树节点,用dfn[v]更新low[x]。(注意,和我们前面分析流程不一样了)
  4. 若v已访问且不在栈中:说明v已搜索完毕,其所在连通分量已被处理,所以不用对其做操作。
  5. 离x时,记录SCC。只有遍历完一个SCC, 才可以出栈。更新low值的意义:避免SCC的节点提前出栈。

注意我们前面一直讲用low[v]更新low[x],但是上面流程中用了dfn[v]来更新,如果换成low[v]的话可以负责的讲在SCC问题中绝对正确,但是如果是无向图的双连通分量问题就会出错了,这个和割点有关,后续再写另一篇博客介绍。

Tarjan算法代码实现
#define N 10010
#define M 50010
struct edge
{
    int v, nxt;
} edges[M];
int head[N]{0}, idx = 0;
void addedge(int u, int v)
{
    edges[idx] = {v, head[u]};
    head[u] = idx++;
}
int dfn[N]{0}, low[N]{0}, tot = 0; // tot访问节点的时间戳编号
// dfn 时间戳 low节点所能访问的最小时间戳 tot数目
int s[N], top = 0; // 辅助栈
bitset<N> instk;   // 在栈内?
int scc[N], sz[N], cnt = 0;
// scc 节点所在SCC的编号 sz SCC的大小 cntscc数目
int ans = 0;
void tarjan(int x)
{
    dfn[x] = low[x] = ++tot;
    s[top++] = x, instk[x] = 1;
    for (int j = head[x]; ~j; j = edges[j].nxt)
    {
        if (!dfn[edges[j].v])
        {
            tarjan(edges[j].v);
            low[x] = min(low[x], low[edges[j].v]);
        }
        else if (instk[edges[j].v])
        {
            // y要么是祖先要么是横叉边的点
            // 计算强连通dfn可以换成low,但是在计算双连通替换会出错
            low[x] = min(low[x], dfn[edges[j].v]);
        }
    }
    if (low[x] == dfn[x])
    {
        int y;
        cnt++;
        do
        {
            y = s[--top];
            instk[y] = 0;
            scc[y] = cnt;
            sz[cnt]++;
        } while (x != y);
    }
}

OJ练习:

P2863 [USACO06JAN] The Cow Prom S

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1319628.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Qt之使用QListView加载相册(富文本ToolTip)

一.效果 二.实现 #include "mainwindow.h" #include "ui_mainwindow.h"#include <QStandardItemModel> #include <QFont>MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow) {ui->setupUi(this);QFont…

SourceTree 免登录跳过初始设置

用于Windows和Mac的免费Git客户端。 Sourcetree简化了如何与Git存储库进行交互&#xff0c;这样您就可以集中精力编写代码。通过Sourcetree的简单Git GUI可视化和管理存储库。 SourceTree 安装之后需要使用账号登陆以授权&#xff0c;以前是可以不登陆的&#xff0c;但是现在是…

基于ssm办公自动化管理系统论文

摘 要 随着计算机应用的普及、成熟&#xff0c;越来越多公司开始采用网上信息管理系统&#xff0c;网上信息管理系统的运行可以有效的提高企业管理效率。因此&#xff0c;为满足企业办公管理方面的需求&#xff0c;开发了办公自动化管理系统。 本文重点阐述了办公自动化管理系…

c语言:输出1~100的数据以10×10格式

一、题目 以10*10的格式&#xff0c;输出1-100。 如图&#xff1a; 二、思路分析 此题的难点&#xff1a; 1、1-9的要向前空一格&#xff1b; 2、100要向前进一格 三、代码截图【带注释】 四、源代码【带注释】 #include <stdio.h> int main() { //分成三个部分&am…

Axure的交互与情形,事件,动作

交互样式 交互样式是指当用户与原型进行交互时&#xff0c;元素所呈现出的视觉效果。在Axure中&#xff0c;可以通过设置交互样式来调整元素在交互过程中的外观&#xff0c;例如改变颜色、大小、位置等。 交互事件 交互事件是指在用户与原型进行交互时触发的动作。在Axure中&…

计算机图形学头歌合集(题集附解)

目录 CG1-v1.0-点和直线的绘制 第1关&#xff1a;OpenGL点的绘制 第2关&#xff1a;OpenGL简单图形绘制 第3关&#xff1a;OpenGL直线绘制 第4关&#xff1a;0<1直线绘制-dda算法<> 第5关&#xff1a;0<1直线绘制-中点算法<> 第6关&#xff1a;一般直线绘…

使用Log4j与log4j2配置mybatisplus打印sql日志

环境&#xff1a;项目非完全spring项目&#xff0c;没有spring的配置文件。执行sql时老是不打印sql语句。因此进行修改&#xff0c;过程比较坎坷&#xff0c;记录一下。 我尝试使用log4j和log4j2进行配置 最终把这两种全部配置记录上 Log4j配置 如果项目用的是log4j需要进行配置…

nodejs配置express服务器,运行自动打开浏览器

查看专栏目录 Network 灰鸽宝典专栏主要关注服务器的配置&#xff0c;前后端开发环境的配置&#xff0c;编辑器的配置&#xff0c;网络服务的配置&#xff0c;网络命令的应用与配置&#xff0c;windows常见问题的解决等。 文章目录 设置方法&#xff1a;1&#xff0c;安装nodej…

全国软件供应链安全产教融合共同体成立大会在武汉成功举办

为深入学习贯彻党的二十大精神&#xff0c;落实《关于深化现代职业教育体系建设改革的意见》等要求&#xff0c;探索职业教育产教融合创新发展新生态&#xff0c;培养软件供应链安全人才体系&#xff0c;推动教育链、人才链、产业链、创新链的协同发展&#xff0c;12月16日 &am…

Spring 6(二)【IOC原理】

前言 IOC 是Spring的两大核心概念之一&#xff0c;它是一种思想&#xff0c;需要极其熟练的掌握。 今日摘录&#xff1a; 低能无聊的人太多。说他们勤勉&#xff0c;不过是因困为不会合理分配时间&#xff1b;说他们积极&#xff0c;不过是逃避其他困难工作而已。即便说工作只…

20个CobaltStrike实战案例 +插件

案例 1&#xff1a;窃取 token&#xff0c;访问域控或者本地管理员 前提&#xff1a;1.cs 上线的主机要管理员权限 注意点&#xff1a;登录失败时一定要先恢复身份 方式一&#xff1a; Ps #查看进程 steal_token 2020(管理元权限运行的进程号) &#xff0c; shell dir \\dc\c…

UE5:Lumen 框架

1.Lumen渲染流程框架 2.Lumen基本概念 2.1 LumenCard & LumenMeshCards LumenMeshCards&#xff1a;一组带有方向性的模型简化代理&#xff0c;视模型复杂度不同可能包含6个及以上数量的LumenCard&#xff1b;用来提供光照采样的位置和方向。 2.2 LumenCardPage & Lu…

设备制造CRM:一文看懂设备制造行业CRM的作用和优势

设备制造行业客户需求多样化、服务链路长&#xff0c;企业在关注APS、EMS等工业软件之余还要以客户为中心&#xff0c;做好客户服务。设备制造行业CRM管理系统是企业管理客户关系的利器&#xff0c;设备制造行业CRM的作用有哪些&#xff1f;一文带您看懂。 设备制造行业需要解…

kitex快速入门

简介 kitex是字节跳动开源的一款基于 Go语言的rpc框架。 官网 github仓库 gitee地址 安装与使用 kitex具有一键生成的功能&#xff0c;能够一键生成rpc架构&#xff0c;使开发者只关注于逻辑的开发即可。自动生成的源码只需要简单的配置就可使用&#xff0c;十分方便。 安…

能在电脑同时控制苹果和安卓的软件,找到了!

开门见山&#xff0c;既能远程控制安卓手机又能控制iPhone或iPad的软件是AirDroid Cast。 AirDroid Cast是一款专业、强大且易于使用的投屏&控制工具。不仅可以将安卓手机&#xff08;安卓7.0及以上版本&#xff09;、iPhone、iPad的屏幕画面投射到电脑上&#xff0c;还支持…

RED影视级R3D文件变0字节加chkdsk恢复案例

随着千兆网络普及小型存储也开始越来越多&#xff0c;特别是在专业级影视领域&#xff0c;存储数据要的就是快速和稳定&#xff0c;所以小存储很适合专业级影视这个行业。下面我们来看一个36T的小存储恢复R3D文件的案例。 故障存储: 36T&#xff0c;Exfat文件系统 故障现象:…

【C++初阶】八、初识模板(泛型编程、函数模板、类模板)

相关代码gitee自取&#xff1a; C语言学习日记: 加油努力 (gitee.com) 接上期&#xff1a; 【C初阶】七、内存管理 &#xff08;C/C内存分布、C内存管理方式、operator new / delete 函数、定位new表达式&#xff09; -CSDN博客 目录 一 . 泛型编程 二 . 函数模板 函数模板…

【C语言】数组(一维)详解,手把手教你,保姆级!!!

目录 数组的概念 数组的创建 数组的初始化 数组的类型 数组使用下标 数组的打印 数组的输入 数组的储存 总结 数组的概念 数组是⼀组相同类型元素的集合&#xff1b; 从这个概念中我们有3点拓展&#xff1a; 1&#xff0c;数组中存放的是1个或者多个数据&#xff0c;但…

【Linux】进程周边005之环境变量

&#x1f440;樊梓慕&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C》《Linux》 &#x1f31d;每一个不曾起舞的日子&#xff0c;都是对生命的辜负 目录 前言 1.环境变量是什么&#xff1…

引用阿里图标库,不知道对应的图标是什么,可在本地显示图标ui,再也不要担心刚来不知道公司图标对应的是什么了

项目中使用了阿里的图标库&#xff0c;但是无法看到对应显示什么&#xff0c;每次都要去阿里图标库里面找 在下载下来的文件中会发现有两个文件一个是iconfont.css和iconfont.json&#xff0c; 这两个文件的数据可以拿到然后显示在页面上 有两个问题&#xff1a; 1&#xff1a…