Tarjan算法

概念区分

  • 有向图

    • 强连通:在有向图\(G\)中,如果两个顶点\(u, v\ (u \neq v)\)间有一条从\(u\)到\(v\)的有向路径,同时还有一条从\(v\)到\(u\)的有向路径,则称\(u, v\)强连通
    • 强连通图:如果有向图\(G\)的任意两个不同的顶点都强连通,则称\(G\)是一个强连通图
    • 强连通分量:有向图\(G\)的极大强连通子图称为图\(G\)的强连通分量
  • 无向图
    • 连通:和强连通类似(只是无向图的任意边都是双向的,如果存在\(u\rightarrow v\)的路径,必然存在\(v\rightarrow u\)的路径)
    • 连通图:如果无向图\(G\)的任意两不同的顶点都连通,则称\(G\)是一个连通图
    • 连通分量:无向图\(G\)的极大连通子图称为图\(G\)的连通分量
    • 割点(割顶):在无向图\(G\)中,如果删除一个点\(u\)及以\(u\)为端点的所有边后,图的连通分量个数增多,则称点\(u\)为割点(割顶)
    • 桥:在无向图\(G\)中,如果删除一条边$e=(u,v)\ \((连接\)u,v\(两点的边)后,图的连通分量个数增多,则称边\)e$为,也称割边
    • 双连通:
      • 双连通图:分为点双连通图边双连通图,若一个无向图\(G\)中的去掉任意一个节点(一条边)都不会改变图\(G\)的连通性,即不存在割点(桥),则称图\(G\)点(边)双连通图
      • 边双连通图:一个无向图\(G\)中的每一个极大点双连通子图边双连通子图分别称为无向图\(G\)的点双连通分量(BCC)边双连通分量(e-BCC)

Tarjan

DFS​树

\(tarjan\)的过程实际上就是一个\(dfs\)的过程,对图\(G\)进行\(dfs\)会得到一棵\(dfs\)树,在有向图的\(dfs\)树中有四种边,树边、回边(返祖边、后向边)、横叉边和前向边。树边顾名思义,回边是指在\(dfs\)过程中搜索到已经访问过的点,但它的子树未访问完成;横叉边是指搜索到已经访问过的点且它的子树也已访问完成,而且横叉边连接的两个点不是祖先和后代的关系;前向边与横叉边唯一的不同在于前向边连接的两个点祖先和后代的关系,读者可以结合下图理解这四种边

⏫图片来源:https://www.cnblogs.com/gongpixin/p/5003049.html

强连通分量

思想

  • \(dfn[u]\):顶点\(u\)被访问到的时间戳,每个点的\(dfn\)被赋值后就不会改变

  • \(low[u]\):顶点\(u\)能够到达的点中的\(dfn\)最小值

  • \(stack[top]\):可能构成强连通分量的点的集合

  • \(vis[u]:\)顶点\(u\)是否在栈中

  • \(color[u]\):顶点\(u\)的颜色,用于区分不同的强连通分量

    // 有些题目添加\(color\)数组比较方便,不是必要的

采用深度优先搜索的思想,对每一个可能的强连通分量进行\(dfs\):维护一个可能构成强连通分量的集合\(stack\),将搜索到的点加入\(stack\),并维护其\(dfn,\ vis\)值,往下搜它的边连接的顶点,在回溯的时候维护\(low\)的值,如果点\(u\)的\(dfn==low\)说明它是某个强连通分量子树的根,此时我们找到了一个强连通分量,只要将栈\(stack\)中的点弹出,直到弹出\(u\)

  • 同一个强连通分量中的点的\(low\)值是相同的

看到这儿,有些读者可能会问,那无向图的连通分量咋求?也是直接套\(tarjan\)的板子吗?如果您有这样困惑反正我是有过,那是学算法学傻了不是,无向图还\(tarjan\)啥,直接\(dfs\)或\(bfs\)一遍就完了,那善于思考的读者们又会问了,为啥无向图这么方便?因为对于无向图,如果存在路径\(u\rightarrow v\),那路径\(v\rightarrow u\)必然存在,\(u\)能跑到的点(包括\(u\)自己)都是一个连通分量里的

板子

有向图强连通分量

void tarjan(int u) {
dfn[u] = low[u] = ++tim;
vis[u] = true; // 入栈
stack[++top] = u;
int size = g[u].size();
for(int i = 0; i < size; ++i) {
int v = g[u][i];
if(!dfn[v]) { // 树边,继续下搜
tarjan(v);
low[u] = min(low[u], low[v]);
} else if(vis[v]) // 回边,更新low[u]
low[u] = min(low[u], dfn[v]);
}
if(dfn[u] == low[u]) {
color[u] = ++sum;
vis[u] = false;
while(stack[top] != u) {
color[stack[top]] = sum;
vis[stack[top--]] = false;
}
top--;
}
} //for(int i = 1; i <= n; ++i)
// if(!dfn[i])
// tarjan(i);

割点(顶) / 桥

原理

在\(tarjan\)算法求割点的过程中,主要涉及到两种边:树边和回边(返祖边),意义已在上文中提到⏫

  • 定理\(1\):在无向图\(G\)中,点\(u(u不是根节点)\)是割点 \(\iff\) \(u\)的某个子树\(T\)中不含有返回\(u\)的祖先(不包括\(u\))的边 \(\iff\) \(low[v] >= dfn[u]\)

    特别地,根节点是割顶 \(\iff\) 它的子节点数目大于\(1\)

    画图YY一下:

    上图的\(u\)的某个子树\(T\)中虽然有返回\(u\)的回边,但是没有返回\(u\)的祖先(不包含\(u\))的回边,所以\(u\)是割点,但是边\(e\)并不是桥

  • 定理\(2\):在无向图\(G\)中,边\((u,v)\)是桥 \(\iff\) \(u\)的某个子树\(T\)中不含有返回\(u\)的祖先(包括\(u\))的边 \(\iff\) \(low[v] > dfn[u]\)

    还是看之前的图,\(fa\)的某个子树中不含有返回\(fa\)的祖先(包括\(fa\))的回边,所以\(ef\)是桥

  • 可以看出如果一个图\(G\)有桥,它必有割点,因为桥连接的两端必然至少有一个是割点,但反过来不成立

板子

还有点细节:

\(child\)是点\(fa\)的子节点个数,注意\(fa\)的孙子不计算在内

特别的,像:,点\(u\)不是割点,因为去掉它后,这个图的强连通分量的个数并没有增加

void Tarjan(int u, int fa) { // 割点
low[u] = dfn[u] = ++tim;
int sz = g[u].size(), child = 0;
for(int i = 0; i < sz; i++) {
int v = g[u][i];
if(!dfn[v]) {
Tarjan(v, fa);
low[u] = min(low[v], low[u]);
if(low[v] >= dfn[u] && u != fa) iscut[u] = true;
// if(low[v] > dfn[u]) u - v 是桥
if(u == fa) child++;
} else
low[u] = min(low[u], dfn[v]);
}
if(fa == u && child >= 2) iscut[u] = true;
} //for(int i = 1; i <= n; ++i)
// if(!dfn[i])
// tarjan(i, i);

缩点

啥是缩点

就把一个强连通分量(或边双连通分量等,视题目而定)缩成一个点

再看张图YY一下:

光这么讲,大家肯定还是云里雾里,这缩点到底能干啥?下面我们通过两道例题来看看缩点到底能干啥

例题

  • 洛谷P3387 缩点

    思路:缩点,把一个强连通分量缩成一个点,新点的权值等于强连通分量里的所有点的点权之和,缩完点就能得到一个有向无环图\((DAG)\),然后就可以在这个\(DAG\)上跑\(dp\)了,然后就能得到答案

双连通分量

一点性质

  • 边双连通分量(e-BCC)满足任意两点间都有至少两条边不重复的路径
  • 点双连通分量(BCC)满足任意两点间都有至少两条点不重复路径
  • 有桥必有割点,一个点双连通分量必然是边双连通分量,反之,不一定成立

边双连通分量(e-BCC)

如上图,左右两个用大圈圈出来的分别是两个边双连通分量

求法:

把桥全部去掉剩下的独立的分量都是边双连通分量,所以只要一遍\(tarjan\)找到桥,再一遍\(dfs\)就能能求出边双连通分量

点双连通分量(BCC)

如上图,左边红圈圈出的和紫色圈圈出的分别是两个点双连通分量,红点是整张图的一个割点

两个点双连通分量间至多有一个公共点,且它一定是割点;不是割点的点,一定属于某个点双连通分量

求法:

只要分离了割点,就可以把点双连通分量分离出来,但是一个割点可能同属于两个点双连通分量,所以一般用分量中的边来输出,可以用栈保存遍历到的边,一旦发现割顶就弹出属于一个BCC的边

例题

  • POJ3352 Road Construction

    思路:题意是问最少加几条边可以使整个图变成边双连通的,即问最少加多少条边可以使整张图的任意两点间至少有两条边不重复路径,所以我们可以将已有的边双连通分量缩点,缩完点形成一棵树。现在,题目变为在缩完点后的树上最少添加几条边能使其变为边双连通图,有一个结论是对于一棵无向树,我们要使得其变成边双连通图,需要添加的边数= (树上度数为1的点的个数+1)/2

Tarjan算法——强连通、双连通、割点、桥的更多相关文章

  1. 算法笔记_150:图论之双连通及桥的应用(Java)

    目录 1 问题描述 2 解决方案   1 问题描述 Description In order to get from one of the F (1 <= F <= 5,000) graz ...

  2. Tarjan算法初探(3):求割点与桥以及双连通分量

    接上一节Tarjan算法初探(2):缩点 在此首先提出几个概念: 割点集合:一个无向连通图G 若删除它的一个点集 以及点集中所有点相连的边(任意一端在点集中)后 G中有点之间不再连通则称这个点集是它的 ...

  3. tarjan算法--求解无向图的割点和桥

    1.桥:是存在于无向图中的这样的一条边,如果去掉这一条边,那么整张无向图会分为两部分,这样的一条边称为桥 也就是说 无向连通图中,如果删除某边后,图变成不连通,则称该边为桥 2.割点:无向连通图中,如 ...

  4. tarjan算法--求无向图的割点和桥

    一.基本概念 1.桥:是存在于无向图中的这样的一条边,如果去掉这一条边,那么整张无向图会分为两部分,这样的一条边称为桥无向连通图中,如果删除某边后,图变成不连通,则称该边为桥. 2.割点:无向连通图中 ...

  5. Tarjan求强连通分量、求桥和割点模板

    Tarjan 求强连通分量模板.参考博客 #include<stdio.h> #include<stack> #include<algorithm> using n ...

  6. 图的强连通&双连通

    http://www.cnblogs.com/wenruo/p/4989425.html 强连通 强连通是指一个有向图中任意两点v1.v2间存在v1到v2的路径及v2到v1的路径. dfs遍历一个图, ...

  7. tarjan算法强连通分量的正确性解释+错误更新方法的解释!!!+hdu1269

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1269 以下内容为原创,转载请声明. 强连通分量SCC(Strongly Connected Compo ...

  8. Tarjan算法--强连通分量

    tarjan的过程就是dfs过程. 图一般能画成树,树的边有三种类型,树枝边 + 横叉边(两点没有父子关系) + 后向边(两点之间有父子关系): 可以看到只有后向边能构成环,即只有第三张图是强连通分量 ...

  9. HDU4612(Warm up)2013多校2-图的边双连通问题(Tarjan算法+树形DP)

    /** 题目大意: 给你一个无向连通图,问加上一条边后得到的图的最少的割边数; 算法思想: 图的边双连通Tarjan算法+树形DP; 即通过Tarjan算法对边双连通缩图,构成一棵树,然后用树形DP求 ...

随机推荐

  1. 「UVA1185」Big Number 解题报告

    UVA1185 Big Number In many applications very large integers numbers are required. Some of these appl ...

  2. Web开发学习目录

    1.MyBaits 1.1mybatis学习入门 1.2mybatis学习之CURD 1.3mybatis学习之连接池 1.4mybatis学习之动态SQL 1.5mybatis学习之多表操作 1.6 ...

  3. 小小知识点(十七)——对数形式功率(dBm)与非对数形式功率(w)之间的换算关系

    摘自https://blog.csdn.net/shij19/article/details/52946454 dBm 物理含义是:一个表示功率绝对值的值(也可以认为是以1mW功率为基准的一个比值) ...

  4. python I/O编程

    1.文件读写 使用open打开文件,f=open('/user/test.txt','r'),r表示可读 如果文件不存在,则抛出IOError 文件打开,则用read()方法进行读取 最后关闭用clo ...

  5. linux下安装mysql5.7.25详细教程

    前言 最近项目上线,开始给用户测试了.搞下来好多台服务器,自然要装一个mysql的服务器.想想广大博友应该都会遇到如何装mysql的问题,就此分享,给大家一个安装指南.供大家以后安装的时候,提高效率, ...

  6. .gitignore使用

    一.简绍 我们做的每个Git项目中都需要一个".gitignore"文件,这个文件的作用就是告诉Git哪些文件不需要添加到版本管理中.比如我们项目中的npm包(node_modul ...

  7. Scala实践3

    一.函数式对象 1.1  rational类的规格和创建 Rational类来源于有理数(rational number),来表示n(分子)/d(分母)的数字,同时对有理数的运算(加减乘除)建模,还具 ...

  8. js获取url地址栏参数的方法,解决中文乱码问题,能支持中文参数

    第一种:参数只能是英文的 function getQuery(name) { var reg = new RegExp("(^|&)" + name + "=([ ...

  9. 256位AES加密和解密

    /// <summary> /// 256位AES加密 /// </summary> /// <param name="toEncrypt">& ...

  10. jav设计模之的动态代理

    在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的 ...