tarjan算法

原理:

我们考虑 DFS 搜索树与强连通分量之间的关系。

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

反证法:假设有个结点 在该强连通分量中但是不在以 为根的⼦树中,那么 到 的路径中肯 定有⼀条离开⼦树的边。但是这样的边只可能是横叉边或者反祖边,然⽽这两条边都要求指向的结点已 经被访问过了,这就和 是第⼀个访问的结点⽭盾了。得证。

思路:

在 Tarjan 算法中为每个结点 维护了以下⼏个变量:

1:dfn[u]深度优先搜索遍历时结点 的DFS序。

2:low[u]设以u为根的⼦树为Subtree[u]。low[u]定义为以下结点的dfn的最⼩值:

Subtree(u)中的结点;从 Subtree(u)通过⼀条不在搜索树上的边能到达的结点。

遍历时维护栈,⽤于求解强连通分量。 ⼀个结点的⼦树内结点的 dfn 都⼤于该结点的 dfn。 从根开始的⼀条路径上的 dfn 严格递增,low 严格⾮降。 按照深度优先搜索算法搜索的次序对图中所有的结点进⾏搜索。

在搜索过程中,对于结点u和与其v相邻的结点 考虑 3 种情况:

1. v未被访问:继续对v进⾏深度搜索。在回溯过程中,low[v]⽤low[u]更新 。因为存在从u到v的直接路径,所以v 能够回溯到的已经在栈中的结点,u也⼀定能够回溯到。

2. v被访问过,已经在栈中:即已经被访问过,根据low值的定义(能够回溯到的最早的已经在栈中 的结点),则⽤dfn[u]更新low[v] 。

3. v被访问过,已不在在栈中:说明v已搜索完毕,其所在连通分量已被处理,所以不⽤对其做操作。

代码实现:

void tarjan(int x){
dfn[x]=low[x]=++tim;//DFS序的赋值
sta[++top]=x;vis[x]=1;//入栈
sd[x]=x;//如果这个点不是强连通分量,缩点后它还是自己
for(int i=head[x];i;i=eg[i].nex){
int y=eg[i].to;
if(!dfn[y]){//v未被访问,继续对v进行深度搜索
tarjan(y);
low[x]=min(low[x],low[y]);//回溯,更新low[x]值
}
else{
if(vis[y]){//被访问过
low[x]=min(low[x],dfn[y]);//回溯,更新low[x]值
}
}
if(dfn[x]==low[x]){//找到一个强连通分量
int y;
while(y=sta[top--]){//依次出栈
sd[y]=x;//缩点
vis[y]=0;//出栈
if(x==y) break;
p[x]+=p[y];//点权集中
}
}
}
}

※:缩点后,整张图就变成了一个DAG,所以tarjan常常和拓扑排序一同使用。

例题就不插了,毕竟板子都还没过...

但!

还有一个问题!

目光聚焦到这几行代码:

if(!dfn[y]){
tarjan(y);
low[x]=min(low[x],low[y]);
}
///////////////////////////////////////////////////
else{
if(vis[y]){
low[x]=min(low[x],dfn[y]);
}
}

为什么一个括号里是low[y],一个是dfn[y]?

在这里,其实都写low[y]也是正确的,但是在割点割边的时候便是有问题的了。

原因:未出现的邻居,可能会连到之前出现过的点,所以是LOW[];已经出现的邻居再次出现,就必然是强连通分量图中的一个点,可能是最小时序最小根,取它的DFN,继续计算LOW

割点

定义:

对于⼀个⽆向图,如果把⼀个点删除后这个图的极⼤连通分量数增加了,那么这个点就是这个图 的割点(⼜称割顶)。

通俗理解,如果去掉割点能将这个图割成更多小块,这个点就是割点

原理&实现:

⾸先,我们按照 DFS 序给他打上时间戳(访问的顺序)。

这些信息被我们保存在⼀个叫做 dfn 的数组中。 还需要另外⼀个数组 low ,⽤它来存储不经过其⽗亲能到达的最⼩的时间戳。 例如 low[2] 的话是 1, low[5] 和 low[6] 是 3。 然后我们开始 DFS,我们判断某个点是否是割点的根据是:对于某个顶点 ,如果存在⾄少⼀个顶 点 ( 的⼉⼦),使得low[v]>=dfs[u] ,即不能回到祖先,那么u点为割点。 另外,如果搜到了⾃⼰(在环中),如果他有两个及以上的⼉⼦,那么他⼀定是割点了,如果只有 ⼀个⼉⼦,那么把它删掉,不会有任何的影响。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,m;
int idx=0;
int tim;
int root;
struct node {//邻接表建图
int to;
int from;
int nex;
} eg[N]; int head[N];
int low[N],dfn[N];
int vis[N];
int cnt[N]; void add(int x,int y) {//建图
eg[++idx].from=x;
eg[idx].to=y;
eg[idx].nex=head[x];
head[x]=idx;
} void tarjan(int x) {
low[x]=dfn[x]=++tim;
vis[x]=1;
int flag=0;
for(int i=head[x];i;i=eg[i].nex){
int y=eg[i].to;
if(!vis[y]) {
tarjan(y);
low[x]=min(low[y],low[x]);
if(low[y]>=dfn[x]) {//回不到更早的祖先节点
flag++;//统计儿子数
if(x!=root||flag>1) {//如果是根节点,要有2个儿子回不到祖先
cnt[x]=1;//标记割点
}
}
}
else low[x]=min(low[x],dfn[y]);//如果y已经被遍历,则low[x]直接更新
}
} int main() {
cin>>n>>m;
for(int i=1; i<=m; i++) {
int x,y;
cin>>x>>y;
add(x,y);
add(y,x);//建图
}
for(int i=1; i<=n; i++) {
if(!vis[i]) {
root=i;
tarjan(root);
}
}
int ans=0;
for(int i=1; i<=n; i++) {
ans+=cnt[i];
}
cout<<ans<<endl;
for(int i=1;i<=n;i++) {
if(cnt[i]) cout<<i<<" ";
}
return 0;
}

割边

定义:

对于⼀个⽆向图,如果删掉⼀条边后图中的连通分量数增加了,则称这条边为桥或者割边。严谨 来说,就是:假设有连通图 G=V,E, 是其中⼀条边(即 e∈E),如果 G-e是不连通 的,则边 e是图 G的⼀条割边(桥)。

通俗来讲,如果去掉边能将这个图割成更多小块,这条边就是割边(桥)

实现:

实现 和割点差不多,只要改⼀处: low[v]>dfn[u]就可以了,⽽且不需要考虑根节点的问题。 原来我们求割点的时候,发现点 v不经过⽗节点 u就⽆法回到祖先节点,所以顶点u 是割点。

对于边来说,如果⼦节点 只能通过当前这条边到达⽗亲节点 ,则说明当前这条边是割边。但是要 注意的是,我们只改上⾯说的那⼀处,会有问题。我们⽤两条边来表示⽆向图的⼀条边,在搜索 的时 候会通过反边往⽗亲 ⾛,如果这个时候更新 的话,将会导致错误。所以需要特判反边(注意,如 果⽤特判⽗亲节点的⽅法规避反边,会在重边图出错)。

代码就懒得打了(其实是不会)

图的连通性——Tarjan算法&割边&割点的更多相关文章

  1. 图的连通性--Tarjan算法

    一些概念 无向图: 连通图:在无向图中,任意两点都直接或间接连通,则称该图为连通图.(或者说:任意两点之间都存在可到达的路径) 连通分量: G的 最大连通子图 称为G的连通分量. 有向图 (ps.区别 ...

  2. tarjan求割边割点

    tarjan求割边割点 内容及代码来自http://m.blog.csdn.net/article/details?id=51984469 割边:在连通图中,删除了连通图的某条边后,图不再连通.这样的 ...

  3. Tarjan算法求割点

    (声明:以下图片来源于网络) Tarjan算法求出割点个数 首先来了解什么是连通图 在图论中,连通图基于连通的概念.在一个无向图 G 中,若从顶点i到顶点j有路径相连(当然从j到i也一定有路径),则称 ...

  4. Tarjan算法与割点割边

    目录 Tarjan算法与无向图的连通性 1:基础概念 2:Tarjan判断割点 3:Tarjan判断割边 Tarjan算法与无向图的连通性 1:基础概念 在说Tarjan算法求解无向图的连通性之前,先 ...

  5. tarjan算法(割点/割边/点连通分量/边连通分量/强连通分量)

    tarjan算法是在dfs生成一颗dfs树的时候按照访问顺序的先后,为每个结点分配一个时间戳,然后再用low[u]表示结点能访问到的最小时间戳 以上的各种应用都是在此拓展而来的. 割点:如果一个图去掉 ...

  6. Tarjan 算法求割点、 割边、 强联通分量

    Tarjan算法是一个基于dfs的搜索算法, 可以在O(N+M)的复杂度内求出图的割点.割边和强联通分量等信息. https://www.cnblogs.com/shadowland/p/587225 ...

  7. 学习笔记--Tarjan算法之割点与桥

    前言 图论中联通性相关问题往往会牵扯到无向图的割点与桥或是下一篇博客会讲的强连通分量,强有力的\(Tarjan\)算法能在\(O(n)\)的时间找到割点与桥 定义 若您是第一次了解\(Tarjan\) ...

  8. tarjan算法应用 割点 桥 双连通分量

    tarjan算法的应用. 还需多练习--.遇上题目还是容易傻住 对于tarjan算法中使用到的Dfn和Low数组. low[u]:=min(low[u],dfn[v])--(u,v)为后向边,v不是u ...

  9. 图之强连通--Tarjan算法

    强连通分量 简介 在阅读下列内容之前,请务必了解图论基础部分. 强连通的定义是:有向图 G 强连通是指,G 中任意两个结点连通. 强连通分量(Strongly Connected Components ...

随机推荐

  1. 风炫安全WEB安全学习第二十三节课 利用XSS获取COOKIE

    风炫安全WEB安全学习第二十三节课 利用XSS获取COOKIE XSS如何利用 获取COOKIE 我们使用pikachu写的pkxss后台 使用方法: <img src="http:/ ...

  2. 【小菜学网络】MAC地址详解

    上一小节介绍了以太网帧的结构,以及帧中各个字段的作用.参与以太网通讯的实体,由以太网地址唯一标识.以太网地址也叫做 MAC 地址,我们对它仍知之甚少. 以太网地址在不同场景,称谓也不一样,常用叫法包括 ...

  3. Jetbrains系列产品重置试用方法

    0x0. 项目背景 Jetbrains家的产品有一个很良心的地方,他会允许你试用30天(这个数字写死在代码里了)以评估是否你真的需要为它而付费.但很多时候会出现一种情况:IDE并不能按照我们实际的试用 ...

  4. Java的nanoTime()方法

    java有两个获取和时间相关的秒数方法,一个是广泛使用的 System.currentTimeMillis() 返回的是从一个长整型结果,表示毫秒. 另一个是 System.nanoTime() 返回 ...

  5. Goland 设置代码格式化

    前言 之前一直喜欢 VsCode 的代码自动格式化和其他的一些功能 今天了解到原来 Goland 也有这些功能, 想想也对, 毕竟这么大 正文 Goland设置代码格式化 进入设置,按需选择要使用的, ...

  6. 魔法方法推开Python进阶学习大门

    热爱Python Python是Guido van Rossum设计出来的让使用者觉得如沐春风的一门编程语言.2020年11月12日,64岁的Python之父宣布由于退休生活太无聊,自己决定加入Mic ...

  7. ctfshow——web_AK赛

    签到_观己 从题目描述中没发现什么有用的信息 发现文件包含 尝试使用PHP伪协议执行命令,发现无法执行 尝试使用远程文件包含,发现也未开启 尝试使用日志注入 记录了UA值,抓包写入一句话木马 使用蚁剑 ...

  8. 关于postgresql中numeric和decimal的精度和标度问题

    精度即数的有效数字个数 2.5的有效数字个数是2,但是053.2的有效数字个数是3 标度是小数点的位数 例如numeric(2,1),即这个数必须是两位,并且小数后面最多有一位,多出来的小数会被四舍五 ...

  9. SAP密码策略挺有意思

    很多系统管理员可能都知道通过RZ10可以配置SAP的密码策略.例如:密码里包含的大小写字符.数字.特殊字符.密码长度.密码不能和前多少次的密码相同.不能和之前的密码有多少位相似等但是你知道吗?其实还有 ...

  10. Java中的基本数据类型与引用数据类型

    一.基本数据类型 byte.short.int.long(整数类型) float.double(浮点数类型) char(字符型) boolean(布尔类型 ) Java数据大多数存放在堆栈中. 栈区: ...