[笔记]Tarjan

news/2024/11/16 9:54:32/文章来源:https://www.cnblogs.com/Sinktank/p/18386301

Tarjan求强连通分量

定义

DFS树相关

我们对一个有向联通图进行DFS遍历,会得到一棵DFS树

DFS树的形态是根据我们DFS的顺序来决定的,因此对一个有向联通图来说,它的DFS树可能有多个。我们把这棵树的边称作树边

其他边我们分为\(3\)类:

  • 前向边:从\(u\)到它dfs树上的祖先的边。
  • 后向边:从\(u\)到它dfs树上的后代的边。
  • 交叉边:除了前向边、后向边,剩下就是交叉边了。

如下图所示:

强连通分量

称一个有向图是强连通的,当且仅当任意两个节点可以互相到达。

强连通分量就是一个有向图的强连通子图。

上图的强连通分量找出来就是这样的:

正片

Tarjan算法用一次DFS完成,用\(1\)个栈来存储当前遍历过的节点中,还没找到强连通分量的节点。

我们为节点\(i\)定义\(2\)个属性:

  • \(dfn[i]\):节点\(i\)的时间戳,即\(i\)是第几个被搜索到的节点。
  • \(low[i]\):节点\(i\)通过至多\(1\)条非树边,能到达的在栈中的节点的最小时间戳。

接下来任意选定\(1\)个节点开始DFS,遍历到的每个节点入栈,并初始化\(dfn[i]=low[i]\),都为该节点被遍历的次序。对于节点\(u\),枚举它能直接到达的节点\(i\)

  • 如果\(i\)未被访问:那么对\(i\)进行搜索;由于\(u\)能直接到达\(i\),所以根据定义,\(low[u]=\min(low[u],low[i])\)
  • 如果\(i\)被访问过:
    • 如果\(i\)在栈中:说明\(u\)通过一条非树边到达\(i\),那么根据定义,\(low[u]=\min(low[u],dfn[i])\)
    • 如果\(i\)不在栈中:说明\(i\)所在的强连通分量已经被找到,所以什么也不用做。

如果完成节点\(u\)的搜索,发现\(dfn[u]=low[u]\),则说明\(u\)是一个强连通分量中时间戳最小的,因此不断出栈直到\(u\)(包括\(u\))为止,出栈的所有节点是一个强连通分量。

代码:

int low[N],dfn[N],tim;//tim是不断增加时间戳
bool vis[N];//vis[i]表示i是否在栈中
stack<int> st;
void tarjan(int x){low[x]=dfn[x]=++tim;st.push(x);vis[x]=1;for(auto i:G[x]){if(!dfn[i]){//没访问过tarjan(i);low[x]=min(low[x],low[i]);}else if(vis[i]){//访问过,且在栈中low[x]=min(low[x],dfn[i]);}}if(dfn[x]==low[x]){while(1){int t=st.top();st.pop();vis[t]=0;if(t==x) break;}}
}

然而只选定\(1\)个节点不一定能处理到所有节点,因此主函数中我们要这么写:

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

一些问题&思考

\(\textbf{1.}\)主函数中循环的写法为什么是正确的?

首先可以发现,主函数中每完成一次搜索,栈都是空的,因为搜索过程中,如果\(i\)已经处理过了就什么都不做(情况2.2),所以强连通分量之间互不干扰,已经搜出的强连通分量,可以直接看作从图上被删掉。自然根节点的\(low\)不会得到更新,搜索结束栈会被清空,所以不会出现“处理了一半的强连通分量”。

\(\textbf{2.}\)为什么\(low\)的定义必须限制“最多经过\(1\)条非树边”,为什么是正确的?不加限制会影响正确性吗?

实际上如果不加限制的话,是没法递推的。如下图:

我们从节点\(1\)开始搜索,现在正在搜索节点\(3\)\(3\)有一条连接\(2\)的边,如果\(low\)的定义不加限制,\(low[3]\)应该和\(low[2]\)取最小值。可是\(low[2]\)还没有求出来,它的值可能是\(1\)也可能是\(2\),取决于搜索的顺序。这样子就有后效性了,是无法递推的。

接下来证明Tarjan算法的正确性,即“按顺序搜索,如果遇到\(low[i]=dfn[i]\)则不断出栈”是可行的。
假设现在完成了对子树\(u\)的搜索,现在有\(low[u]=dfn[u]\),我们可以把除\(u\)以外的所有点分成\(5\)类:

  • 没访问过的。
  • 访问过,但现在不在栈中。
  • 访问过,现在在栈中,\(u\)的上方。
  • 访问过,现在在栈中,\(u\)的下方。

我们要证明的是“\(u\)和第\(3\)类节点构成一个强连通分量”。

即:证明\(u\)和第\(3\)类节点互相可达,而且与其他类型的节点不互相可达。

  • \(\bf{Type 1}\):没访问过的
    我们需要证明此类节点和\(u\)不能互相到达。
    • 既然\(u\)的子树已经搜索完了,那么还没有访问到的节点,就是从\(u\)走不到的节点了。
  • \(\bf{Type 2}\):访问过,但现在不在栈中
    如果我们用“此类节点属于其他强连通分量”来解释,就算是循环论证了。我们仍然需要证明此类节点和\(u\)不能互相到达。
    • 设该节点为\(v\),则分类讨论一下:
      • 如果\(dfn[v]<dfn[u]\),那么从\(v\)出发一定到不了\(u\)。我们假设\(v\)能到达\(u\),又因为\(v\)\(u\)先遍历到,所以\(v\)一定是\(u\)的祖先节点(思考一下,如果子树\(A\)比子树\(B\)先遍历到,那么一定不存在\(A\)\(B\)的边),自然应该在栈中(\(u\)的下方),产生矛盾。
      • 如果\(dfn[v]>dfn[u]\),那么从\(v\)出发一定到不了\(u\)。因为我们刚遍历完\(u\)的子树,所以时间戳比\(u\)大的都是\(u\)的后代,自然\(v\)也是。我们设\(v\)能到达的最早节点为\(w\)(留心定义,和\(low\)不太一样):
        • 如果\(dfn[w]<dfn[u]\),那么显然有\(low[u]<dfn[u]\),与\(low[u]=dfn[u]\)矛盾。
          如果有点蒙,你可以想象\(v\)沿着\(low[v]\)一直往上跳,直到\(low[v]<dfn[u]\)为止,此时的\(v\)仍然是\(u\)的后代,而\(u\)又是\(low[v]\)所代表节点的后代。自然\(low[u]\)会更新为\(low[v]\)
        • 如果\(dfn[w]>dfn[u]\),那么\(v\)最远只能到\(u\)的后代\(w\),所以到达不了\(u\)
  • \(\bf{Type 3}\):访问过,现在在栈中,\(\bf{u}\)的上方
    证明此类节点和\(u\)能互相到达。
    • 这类节点因为是\(u\)的后代,所以\(u\)可以到达它们;反过来,这些节点可以到达\(u\)吗?是可以的。如果此类节点\(v\)能到达的最早节点是\(w\):

      • \(dfn[w]<dfn[u]\),和\(\bf{2.2.1}\)情况类似,会出现矛盾。
      • \(dfn[w]>dfn[u]\),和\(\bf{2.2.2}\)类似,那一定在\(u\)搜索完之前被出栈出去了,与“在栈中”矛盾。

      上面两种情况都不存在,因此所有\(v\)都能到达\(u\)

  • \(\bf{Type 4}\):访问过,现在在栈中,\(\bf{u}\)的下方
    证明此类节点不能和\(u\)互相到达。

这些点时间戳一定比\(u\)小,如果\(u\)能到达它们之中任何一个,就有\(low[u]<dfn[u]\),与\(low[u]=dfn[u]\)矛盾。

综上\(u\)只和第\(3\)类节点互相连通,故可行性得证。

\(\textbf{3.}\)似乎“访问过,在栈中”这种情况,用\(low[i]\)更新\(low[x]\)也是可行的?

https://www.acwing.com/blog/content/1819/

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Lecture 12 Real-time Ray Tracing

Lecture 12 Real-Time Ray Tracing Basic ideasample per pixel PPS 1 SPP path tracing = $$\downarrow$$camera出发打到求出第一个交点(像素上看到的点),这一步是primary ray(工业上实际用rasterization)工业上这一步有一个技巧 将这一步改为光栅化 因为每个像素都要从c…

gcc/g++编译ZR

编译工具链我们写程序的时候用的都是集成开发环境 (IDE: Integrated Development Environment),集成开发环境可以极大地方便我们程序员编写程序,但是配置起来也相对麻烦。在 Linux 环境下,我们用的是编译工具链,又叫软件开发工具包(SDK:Software Development Kit)。Linux 环…

一种优雅的方式整合限流、幂等、防盗刷

大家在工作中肯定遇到过接口被人狂刷的经历,就算没有经历过,在接口开发的过程中,我们也需要对那些容易被刷的接口或者和会消耗公司金钱相关的接口增加防盗刷功能。例如,发送短信接口以及发送邮件等接口,我看了国内很多产品的短信登录接口,基本上都是做了防盗刷,如果不做…

Lecture 06 Real-time Environment Mapping (Precomputed Radiance Transfer)

Lecture 06 Real-time Environment Mapping (Precomputed Radiance Transfer) Shadow from environment lighting通常情况下要实时渲染非常困难 在不同观察方向上As a many-light problem: Cost of Shadow Map is linearly to #light As a sampling problem: Visibility项V会变…

Lecture 07 Real-time Global Illumination (in 3D)

Lecture 07 Real-time Global Illumination (in 3D) 实时渲染中全局光照一般只bounce两次(直接光照bounce一次,间接光照bounce两次)primary light source 真正的光源secondary light source 次级光源: 一切被直接光照照到的物体都会继续将自己作为光源想要用间接光照照亮\(…

Lecture 04 Real-time Shadows 2

Lecture 04 Real-time Shadows 2 PCF and PCSS PCF背后的数学知识 Filter / convolution: 如果对某个函数\(f\)做卷积,可以用\([\omega * f](p)=\underset{q \in \Nu (p)}{\sum}w(p,q)f(q)\)比如PCSS中对某一点q周围区域做卷积求visibility \(V(x)=\underset{q\in \Nu(p)}{\su…

Lecture 05 Real-time Environment Mapping

Lecture 05 Real-time Environment Mapping Recap: Environment Lighting一张表示了来自四面八方的无穷远处光(distance lighting)的图片 Spherical map vs. cube mapShading from environment lighing 非正式地命名为Image-Based Lighting (IBL) \[L_o(p,\omega_o)=\int_{\Ome…

Lecture 02 Recap of CG Basics

Lecture 02 Recap of CG Basics Graphics Pipeline光栅化、深度测试、Blinn-Phong模型、纹理映射&插值 OpenGL 总结:每一个pass定义物体、相机、MVP 定义帧缓冲区、输入输出纹理 定义Vertex Shader / Fragment Shader 渲染其他的多趟pass 如ShadowMapShading Languages Sh…

VulNyx - System

扫描发现 2121是ftp端口 8000 http的一个端口 6379redis端口爆破redis的密码爆破出来时bonjour猜测ftp的密码和redis的密码是一样的 尝试用密码去爆出ftp的用户名报出来用户名是ben那么根据现有的条件 我们可以利用ftp上传文件 可以用redis module load 加载文件那么我们可以用…

Lecture 03 Real-time Shadows 1

Real-time Shadows 1 Recap: shadow mapping Shadow Mapping2-Pass AlgorithmThe light pass generates the shadow map the camera pass uses the shadow mapAn image-space algorithm好处:无需场景中的几何信息 坏处:导致自遮挡和走样问题PassPass 1: Render from light从光…

[转]高斯-牛顿算法

Gauss-Newton算法是解决非线性最优问题的常见算法之一,最近研读开源项目代码,又碰到了,索性深入看下。本次讲解内容如下:基本数学名词识记 牛顿法推导、算法步骤、计算实例 高斯牛顿法推导(如何从牛顿法派生)、算法步骤、编程实例 高斯牛顿法优劣总结 一、基本概念定义 1.非…

USB TCPM

USB TCPM(Type-C Port Manager)的主要作用是管理 USB Type-C 端口的连接和电源传输协议(USB Power Delivery, PD),确保设备正确识别、协商和切换数据传输和电源供应的角色。TCPM 在 USB Type-C 连接中起到关键管理作用,主要职责包括:管理 USB Type-C 插拔检测:检测设备…