有向图的强连通分量算法
连通图的割点、割边(桥)、块、缩点,有向图的强连通分量

连通图的割点、割边(桥)、块、缩点,有向图的强连通分量一、基本概念无向图割点:删掉它之后(删掉所有跟它相连的边),图必然会分裂成两个或两个以上的子图。
块:没有割点的连通子图割边:删掉一条边后,图必然会分裂成两个或两个以上的子图,又称桥。
缩点:把没有割边的连通子图缩为一个点,此时满足任意两点间都有两条路径相互可达。
求块跟求缩点非常相似,很容易搞混,但本质上完全不同。
割点可以存在多个块中(假如存在k个块中),最终该点与其他点形成k个块,对无割边的连通子图进行缩点后(假设为k个),新图便变为一棵k个点由k-1条割边连接成的树,倘若其中有一条边不是割边,则它必可与其他割边形成一个环,而能继续进行缩点。
有割点的图不一定有割边,如:3是割点,分别与(1,2)和(4,5)形成两个无割点的块有割边的图也不定有割点,如:w(1,2)为割边,有向图强连通分量:有向图中任意两点相互可达的连通子图,其实也相当于无向图中的缩点二、算法无向图借助两个辅助数组dfn[],low[]进行DFS便可找到无向图的割点和割边,用一个栈st[]维护记录块和“缩点”后连通子图中所有的点。
dfn[i]表示DFS过程中到达点i的时间,low[i]表示能通过其他边回到其祖先的最早时间。
low[i]=min(low[i],dfn[son[i]])设 v,u之间有边w(v,u),从v->u:如果low[u]>=dfn[v],说明v的儿子u不能通过其他边到达v的祖先,此时如果拿掉v,则必定把v的祖先和v的儿子u,及它的子孙分开,于是v便是一个割点,v和它的子孙形成一个块。
如果low[u]>dfn[v]时,则说明u不仅不能到达v的祖先,连v也不能通过另外一条边直接到达,从而它们之间的边w(v,u)便是割边,求割边的时候有一个重边的问题要视情况处理,如果v,u之间有两条无向边,需要仍视为割边的话,则在DFS的时候加一个变量记录它的父亲,下一步遇到父结点时不扩展回去,从而第二条无向重边不会被遍历而导致low[u]==dfn[v] ,而在另外一些问题中,比如电线连接两台设备A,B 如果它们之间有两根电线,则应该视为是双连通的,因为任何一条电线出问题都不会破坏A和B之间的连通性,这个时候,我们可以用一个used[]数组标记边的id,DFS时会把一条无向边拆成两条有向边进行遍历,但我们给它们俩同一个id号,在开始遍历v->u前检查它的id是否在上一次u->v 时被标记,这样如果两点之间有多条边时,每次遍历都只标记其中一条,还可以通过其他边回去,形成第二条新的路求割点的时候,维护一个栈st 每遍历到一个顶点v则把它放进去,对它的子孙u如果dfn[u]为0,则表示还没有遍历到则先DFS(u),之后再判断low[u]和dfn[v],如果low[u]>=dfn[v],则把栈中从栈顶到v这一系列元素弹出,这些点与v 形成一个块,如果u的子孙x也是一个割点,这样做会不会错把它们和v,u放在一起形成一个块呢,这种情况是不会发生的,如果发现x是一个割点,则DFS 到x那一步后栈早就把属于x的子孙弹出来了,而只剩下v,u的子孙,它们之间不存在割点,否则在回溯到v之前也早就提前出栈了!画一个图照着代码模拟一下可以方便理解。
(转)全网最!详!细!tarjan算法讲解

(转)全⽹最!详!细!tarjan算法讲解全⽹最详细tarjan算法讲解,我不敢说别的。
反正其他tarjan算法讲解,我看了半天才看懂。
我写的这个,读完⼀遍,发现原来tarjan这么简单!tarjan算法,⼀个关于图的联通性的神奇算法。
基于DFS(迪法师)算法,深度优先搜索⼀张有向图。
!注意!是有向图。
根据树,堆栈,打标记等种种神(che)奇(dan)⽅法来完成剖析⼀个图的⼯作。
⽽图的联通性,就是任督⼆脉通不通。
的问题。
了解tarjan算法之前你需要知道:强连通,强连通图,强连通分量,解答树(解答树只是⼀种形式。
了解即可)不知道怎么办神奇海螺~:嘟噜噜~!强连通(strongly connected):在⼀个有向图G⾥,设两个点 a b 发现,由a有⼀条路可以⾛到b,由b⼜有⼀条路可以⾛到a,我们就叫这两个顶点(a,b)强连通。
强连通图:如果在⼀个有向图G中,每两个点都强连通,我们就叫这个图,强连通图。
强连通分量strongly connected components):在⼀个有向图G中,有⼀个⼦图,这个⼦图每2个点都满⾜强连通,我们就叫这个⼦图叫做强连通分量[分量::把⼀个向量分解成⼏个⽅向的向量的和,那些⽅向上的向量就叫做该向量(未分解前的向量)的分量]举个简单的栗⼦:⽐如说这个图,在这个图中呢,点1与点2互相都有路径到达对⽅,所以它们强连通.⽽在这个有向图中,点1 2 3组成的这个⼦图,是整个有向图中的强连通分量。
解答树:就是⼀个可以来表达出递归枚举的⽅式的树(图),其实也可以说是递归图。
反正都是⼀个作⽤,⼀个展⽰从“什么都没有做”开始到“所有结求出来”逐步完成的过程。
“过程!”神奇海螺结束tarjan算法,之所以⽤DFS就是因为它将每⼀个强连通分量作为搜索树上的⼀个⼦树。
⽽这个图,就是⼀个完整的搜索树。
为了使这颗搜索树在遇到强连通分量的节点的时候能顺利进⾏。
每个点都有两个参数。
1,DFN[]作为这个点搜索的次序编号(时间戳),简单来说就是第⼏个被搜索到的。
转载-Tarjan算法(求SCC)

转载-Tarjan算法(求SCC) 说到以Tarjan命名的算法,我们经常提到的有3个,其中就包括本⽂所介绍的求强连通分量的Tarjan算法。
⽽提出此算法的普林斯顿⼤学的Robert E Tarjan教授也是1986年的图灵奖获得者(具体原因请看本博“历届图灵奖得主”⼀⽂)。
⾸先明确⼏个概念。
1. 强连通图。
在⼀个强连通图中,任意两个点都通过⼀定路径互相连通。
⽐如图⼀是⼀个强连通图,⽽图⼆不是。
因为没有⼀条路使得点4到达点1、2或3。
2. 强连通分量。
在⼀个⾮强连通图中极⼤的强连通⼦图就是该图的强连通分量。
⽐如图三中⼦图{1,2,3,5}是⼀个强连通分量,⼦图{4}是⼀个强连通分量。
其实,tarjan算法的基础是DFS。
我们准备两个数组Low和Dfn。
Low数组是⼀个标记数组,记录该点所在的强连通⼦图所在搜索⼦树的根节点的Dfn值(很绕嘴,往下看你就会明⽩),Dfn数组记录搜索到该点的时间,也就是第⼏个搜索这个点的。
根据以下⼏条规则,经过搜索遍历该图(⽆需回溯)和对栈的操作,我们就可以得到该有向图的强连通分量。
1. 数组的初始化:当⾸次搜索到点p时,Dfn与Low数组的值都为到该点的时间。
2. 堆栈:每搜索到⼀个点,将它压⼊栈顶。
3. 当点p有与点p’相连时,如果此时(时间为dfn[p]时)p’不在栈中,p的low值为两点的low值中较⼩的⼀个。
4. 当点p有与点p’相连时,如果此时(时间为dfn[p]时)p’在栈中,p的low值为p的low值和p’的dfn值中较⼩的⼀个。
5. 每当搜索到⼀个点经过以上操作后(也就是⼦树已经全部遍历)的low值等于dfn值,则将它以及在它之上的元素弹出栈。
这些出栈的元素组成⼀个强连通分量。
6. 继续搜索(或许会更换搜索的起点,因为整个有向图可能分为两个不连通的部分),直到所有点被遍历。
由于每个顶点只访问过⼀次,每条边也只访问过⼀次,我们就可以在O(n+m)的时间内求出有向图的强连通分量。
Kosaraju算法

求有向图的强连通分量的经典算法——Kosaraju算法一、Kosaraju算法步骤:Step1、对有向图G做dfs(深度优先遍历),记录每个结点结束访问的时间Step2、将图G逆置,即将G中所有弧反向。
Step3、按Step1中记录的结点结束访问时间从大到小对逆置后的图做dfs Step4、得到的遍历森林中每棵树对应一个强连通分量。
相关概念:在dfs(bfs)算法中,一个结点的开始访问时间指的是遍历时首次遇到该结点的时间,而该结点的结束访问时间则指的是将其所有邻接结点均访问完的时间。
二、Kosaraju算法求解过程实例下面结合实例说明Kosaraju算法的基本策略。
图1给出了一个有向图G。
图1 有向图GStep1:假设从DFS在遍历时按照字母顺序进行,根据Kosaraju算法,在步骤1中我们得到的遍历顺序可以表达为[A,[C,[B,[D,D],B],C],A][E,[F,[G,[H,H],G],F],E]在遍历序列中每一个结点出现了两次,其中第一次出现的时间为该结点的开始访问时间,第二次出现的时间为该结点的结束访问时间。
Step2:根据算法第2步,将图G逆置,得到对应的反向图G’如图2所示。
Step3:根据步骤1得到的遍历序列,按照结点结束访问时间递减排序后的结果为EFGHACBD下面,按照该结点序列顺序对逆置图G ’所深度优先遍历,得到的深度优先遍历森林如图3所示。
森林中共有4棵树,其中(a)和(d)只有一个结点,这里认为单结点也是一个强联通分量(在实际应用中可以根据实际需要将这种情况过滤掉)。
三、算法讨论问题1:以上图为例,第一遍搜索得到以A 为根的子序列(设为S1)和以E 为根的子树序列(设为S2),图反向后,再从E 开始搜索,能搜到的元素肯定不会包含S1的元素,为什么?答:因为S1中的点都不能到达E ,而第二遍搜索就是看哪些点能到达E ,所以搜不到S1中的点。
问题2:图反向后对A 进行深搜,尽管E 能到达A ,为什么搜不到E ?因为第一遍深搜时,A 不能达到E ,所以E 肯定位于A 的右边,而第二遍深搜是按照结束时间进行搜索的,在搜索A 之前,已经搜完E ,对E 设置了已经遍历标志,所以不会把E 并入A 的强联通分量。
求有向图的强连通分量个数(kosaraju算法)

求有向图的强连通分量个数(kosaraju算法)求有向图的强连通分量个数(kosaraju算法)1. 定义连通分量:在⽆向图中,即为连通⼦图。
上图中,总共有四个连通分量。
顶点A、B、C、D构成了⼀个连通分量,顶点E构成了⼀个连通分量,顶点F,G和H,I分别构成了两个连通分量。
强连通分量:有向图中,尽可能多的若⼲顶点组成的⼦图中,这些顶点都是相互可到达的,则这些顶点成为⼀个强连通分量。
上图中有三个强连通分量,分别是a、b、e以及f、g和c、d、h。
2. 连通分量的求解⽅法对于⼀个⽆向图的连通分量,从连通分量的任意⼀个顶点开始,进⾏⼀次DFS,⼀定能遍历这个连通分量的所有顶点。
所以,整个图的连通分量数应该等价于遍历整个图进⾏了⼏次(最外层的)DFS。
⼀次DFS中遍历的所有顶点属于同⼀个连通分量。
下⾯我们将介绍有向图的强连通分量的求解⽅法。
3. Kosaraju算法的基本原理我们⽤⼀个最简单的例⼦讲解Kosaraju算法显然上图中有两个强连通分量,即强连通分量A和强连通分量B,分别由顶点A0-A1-A2和顶点B3-B4-B5构成。
每个连通分量中有若⼲个可以相互访问的顶点(这⾥都是3个),强连通分量与强连通分量之间不会形成环,否则应该将这些连通分量看成⼀个整体,即看成同⼀个强连通分量。
我们现在试想能否按照⽆向图中求连通分量的思路求解有向图的强连通分量。
我们假设,DFS从强连通分量B的任意⼀个顶点开始,那么恰好遍历整个图需要2次DFS,和连通分量的数量相等,⽽且每次DFS遍历的顶点恰好属于同⼀个连通分量。
但是,我们若从连通分量A中任意⼀个顶点开始DFS,就不能得到正确的结果,因为此时我们只需要⼀次DFS就访问了所有的顶点。
所以,我们不应该按照顶点编号的⾃然顺序(0,1,2,……)或者任意其它顺序进⾏DFS,⽽是应该按照被指向的强连通分量的顶点排在前⾯的顺序进⾏DFS。
上图中由强连通分量A指向了强连通分量B。
算法学习:图论之图的割点,桥,双连通分支

图的割点、桥与双连通分支[点连通度与边连通度]在一个无向连通图中,如果有一个顶点集合,删除这个顶点集合,以及这个集合中所有顶点相关联的边以后,原图变成多个连通块,就称这个点集为割点集合。
一个图的点连通度的定义为,最小割点集合中的顶点数。
类似的,如果有一个边集合,删除这个边集合以后,原图变成多个连通块,就称这个点集为割边集合。
一个图的边连通度的定义为,最小割边集合中的边数。
注:以上定义的意思是,即有可能删除两个或两个以上点的时候才能形成多个连通块![双连通图、割点与桥]如果一个无向连通图的点连通度大于1,则称该图是点双连通的(point biconnected),简称双连通或重连通。
一个图有割点,当且仅当这个图的点连通度为1,则割点集合的唯一元素被称为割点(cut point),又叫关节点(articulation point)。
如果一个无向连通图的边连通度大于1,则称该图是边双连通的(edge biconnected),简称双连通或重连通。
一个图有桥,当且仅当这个图的边连通度为1,则割边集合的唯一元素被称为桥(bridge),又叫关节边(articulation edge)。
可以看出,点双连通与边双连通都可以简称为双连通,它们之间是有着某种联系的,下文中提到的双连通,均既可指点双连通,又可指边双连通。
[双连通分支]在图G的所有子图G’中,如果G’是双连通的,则称G’为双连通子图。
如果一个双连通子图G’它不是任何一个双连通子图的真子集,则G’为极大双连通子图。
双连通分支(biconnected component),或重连通分支,就是图的极大双连通子图。
特殊的,点双连通分支又叫做块。
[求割点与桥]该算法是R.Tarjan发明的。
对图深度优先搜索,定义DFS(u)为u在搜索树(以下简称为树)中被遍历到的次序号。
定义Low(u)为u或u的子树中能通过非父子边追溯到的最早的节点,即DFS序号最小的节点。
Tarjan算法详解

Tarjan算法详解刚学的⼀个新算法,终于有时间来整理⼀下了。
想必都对著名的 ‘太监’ 算法早有⽿闻了吧。
Tarjan Tarjan 算法是⼀种求解有向图强连通分量的算法,它能做到线性时间的复杂度。
实现是基于DFS爆搜,深度优先搜索⼀张有向图。
!注意!是有向图。
然后根据树,堆栈,打标记等种种神奇扯淡⽅法来完成拆解⼀个图的⼯作。
如果要理解Tarjan,我们⾸先要了解⼀下,什么叫做强连通。
强连通 ⾸先,我们将可以相互通达的两个点,称这两个点是强连通的。
⾃然,对于⼀张图,它的每两个点都强连通,那么这张图就是⼀张强连通图。
⽽⼀张有向图中的极⼤强连通⼦图就被命名为强连通分量。
那么,举个栗⼦吃吧。
(盗图⼩丸⼦光荣上线,come from 百度) 对于上⾯的那张有向图来说,{1,2,3,4},{5},{6}分别相互连通,是这张图的三个强连通分量。
算法思想简解 Tarjan是在dfs的基础上将每⼀个强连通分量当做搜索树种的⼀个⼦树的遍历⽅法。
那么我们就可以在搜索的时候,将当前的树中没有访问过的节点加⼊堆栈,然后在回溯的时候判断栈顶到其中的节点是否是⼀个强连通分量。
故⽽我们在遍历的时候,定义如下变量来记录我们的遍历过程。
1.dfn[i]=时间戳(节点i被搜索的次序) 2.low[i]=i或i的⼦树所能寻找到的栈中的节点序号。
所以,当dfn[i]==low[i]时,i或i的⼦树可以构成⼀个强连通分量。
算法图解(盗图愉悦) 在这张图中我们从节点1开始遍历,依次将1, 3,5加⼊堆栈。
当搜索到节点6时,发现没有可以向前⾛的路,开始回溯。
回溯的时候会发现low[5]=dfn[5],low[6]=dfn[6],那么{5},{6}分别是两个强连通分量。
直到回溯⾄节点3,拓展出节点4. 结果⾃然就是拓展到了节点1,发现1在栈中,于是就⽤1来更新low[4]=low[3]=1; 然后回溯节点1,将点2加⼊堆栈中。
强连通分量与模拟链表

强联通分量与模拟链表作者:逸水之寒1.强连通分量强连通分量的定义是:在有向图中,u可以到达v,但是v不一定能到达u,如果u,v 到达,则他们就属于一个强连通分量。
求强连通分量最长用的方法就是Kosaraju算法,比较容易理解而且效率很高,本文对强连通分量的求法均采用Kosaraju算法。
其主要思想:首先对原图G进行深搜形成森林(树),然后选择一棵树进行第二次深搜,注意第一次是要判断节点A能不能通向节点B,而第二次要判断的是节点B能不能通向A,能遍历到的就是一个强连通分量。
(附录给出伪代码)Kosaraju算法如果采用了合适的数据结构,它的时间复杂度是O(n)的。
相关题目有很多,例如USACO 5.3.3,2009NOIP Senior No.3。
下面将以USACO 5.3.3 schlnet 举例说明。
Preblem 1. Network of Schools (USACO 5.3.3 schlnet\IOI96 No.3)A number of schools are connected to a computer network. Agreements have been developed among those schools: each school maintains a list of schools to which it distributes software (the "receiving schools"). Note that ifB is in the distribution list of school A, then A does not necessarily appear in the list of school B.You are to write a program that computes the minimal number of schools that must receive a copy of the new software in order for the software to reach all schools in the network according to the agreement (Subtask A). As a further task, we want to ensure that by sending the copy of new software to an arbitrary school, this software will reach all schools in the network. To achieve this goal we may have to extend the lists of receivers by new members. Compute the minimal number of extensions that have to be made so that whatever school we send the new software to, it will reach all other schools (Subtask B). One extension means introducing one new member into the list of receivers of one school.INPUT FORMATThe first line of the input file contains an integer N: the number of schools in the network (2<=N<=100). The schools are identified by the first N positive integers. Each of the next N lines describes a list of receivers. The line i+1 contains the identifiers of the receivers of school i. Each list ends with a 0. An empty list contains a 0 alone in the line.SAMPLE INPUT (file schlnet.in)52 43 04 5 01 0OUTPUT FORMATYour program should write two lines to the output file. The first line should contain one positive integer: the solution of subtask A. The second line should contain the solution of subtask B.SAMPLE OUTPUT (file schlnet.out)12分析:本题需要将强连通分量进行缩点,具体做法可以先求出所有的强连通分量,然后重新建立图,将一个强连通分量用一个点在新图中表示出来。
有向图的强连通分量及应用

图G T ) ,能遍历 到的顶点就是一个强连通分量。余 下部分和原来 顶弹出 ,所以,得到 出栈 的顶点序列为 v 2 , v 4 , v 3 , v 1 ;再从最后
的森林一起组成一个新的森林 ,继 续步骤 2 直 到没有顶点为止 。 具体求解步骤如下: 其所有邻接 点的访 问都完成 ( 即出栈)的顺序将顶 点排列起来 。
代码为 :
v o i d r ed f s ( mt x 1
_
w h i l e ( e > 0 &&e l f a g [ e ] —= o 1 {
i n t k _ b [ e ] ;
i f ( ! l f a g [ k ] ) d f § ( k ) ; e - n e x t [ e ] ;
1 K o s a r a j u 算法
形成 的 D F S 树森林 嘲。
Ko s a r a j u算 法基于 以下 思想:强连 通分量 一 定是某种 D F S 弹 出,并进入栈 s t a c k 1 ,如图 ( b ) 所示 。 将v 2 从栈顶弹出后,再 这 个算法可 以说是最容 易理解 ,最通 用的算法 ,其 比较关 邻接点 从而从系统栈 弹出,并进入栈 s t a c k l ,如 图 ( c ) 所示 。将 键 的部分是 同时应 用 了原 图 G和反 图 G T 嘲。步骤 1 :先对 原 图 v 4 从栈顶弹 出后 ,顶点 v 3不存在未访 问的邻接点从而从系统栈
图( e ) 所示。这 就是该有向图的两个强连通分量的顶点集 。
பைடு நூலகம்
{ l f a g [ x 】 = 1 ;
i n t e = p [ x 】 ;
( 啦 一个有商翻
∞ 将 肚糟璜弹出
图的连通性

有向图中,极大强连通子图=强连通分量
G1的两个强连通分量 G1
V’是连通图G的一个顶点子集。在G中删去V’及与V’ 相关联的边后图不连通,则称V’是G的割顶集。 最小割顶集中顶点的个数,记成K(G),叫做G的连通度。
规定: K(完全图)=顶点数-1 K(不连通图)=K(平凡图)=0
K(G)=l时,割顶集中的那个顶点叫做割顶。 没有割顶的图叫做块,G中成块的极大子图叫做G的块。
LOW(U)值的计算步骤如下:
在算法执行中,对任何顶点U计算LOW(U)值是不断修改 的,只有当以U为根的dfs子树和后代的LOW值、dfn值产 生后才停止。
图中(7,1)应为(7,7)
如r被选为根,则r 成为割顶当且仅当 它有不止一个儿子 点。
顶点U的标号函数LOW(U): LOW(U)=min{dfn(),LOW(s),dfn(W)} 其中:S是U的一个儿子,(U,W)是后向边
LOW(U)是U或U的后代所能追溯到的最早 (序号小)的祖先结点序号。
顶点U<>r作为图的割顶当且仅当U有一个儿子, 使得LOW(S)>=dfn(U),即S和S的后代不会追溯 到比U更早的祖先点。
G1 K(G1)=0
G2 K(G2)=1
G3 K(G3)=3
G4 K(G4)=5-1=
威廉王迷宫
前向边(实线部分) 后向边(虚线部分) Dfn(x):顶点x被首次访问的次序。 Dfn(x)=I表示x是第I个首次被访问的节点 称为深度优先搜索序数。
Dfs树
如U不是根,U成 为割顶当且仅当 存在U的某一个儿 子顶点S,从S或S 的后代点到U的祖 先点之间不存在 后向边
图的连通性
G1
G2
在无向图中,如果从顶点V到V’有路径,则称V和V’是连通的。 在有向图中,如果从顶点V到V’有路径,并且从V’到V有路径, 则称V和V’是强连通的。 如果对于图中任意两个顶点Vi,Vj,Vi和Vj都是连通的,则称连通图。
图论_连通_连通分量

图论_连通_连通分量 强连通图 : 强连通分量就是本⾝ 有向图 ---> ⾮强连通图 : 多个强连通分量图---> 连通图 : 连通分量就是本⾝ ⽆向图 ---> ⾮连通图 : 多个连通分量路径 : 顾名思义.路径长度 : 路径上边的数量.路径 : 顾名思义.路径长度 : 路径上边的数量.连通 : ⽆向图顶点A可以到顶点B,则称A,B连通.强连通 : 有向图中,两个顶点间⾄少存在⼀条互相可达路径,则两个顶点强连通连通图 : 图中任意两点都连通的图.强连通图 : 有向图的任意两点都强连通.连通分量 : ⽆向图的极⼤连通⼦图称为连通分量.连通图只有⼀个连通分量,即⾃⾝强连通分量: 强连通图有向图的极⼤强连通⼦图.强连通图的强连通分量只有⼀个,即强连通图本⾝.基图 : 将有向图的所有边替换成⽆向边形成的图.弱连通图 : 基图是连通图的有向图.(即,连通的有向图)求图的连通分量的⽬的,是为了确定从图中的⼀个顶点是否能到达图中的另⼀个顶点,也就是说,图中任意两个顶点之间是否有路径可达。
求强连通分量有多种算法.我⽤的Tarjan算法. 复杂度O(V+E)这两个博客写得不错:https:///reddest/p/5932153.htmlhttps:///shadowland/p/5872257.htmlint dfn[16]; // 时间戳int dfn_num = 0; // 时间int low[16]; // 节点u所能访问到的最⼩时间戳int inSt[16]; // 节点u是否在栈中.int st[16];int top = 0;// 我们维护的信息.int col[16]; // 给节点染⾊, 同⼀个连通块的节点应该是同⼀个颜⾊的.int col_num = 0; // 颜⾊值.int size[16]; // 每个颜⾊值所拥有的块数./*第⼀步: 访问当前节点的所有⼦节点: ⼦节点有三种第⼀种: 未访问过的, 我们对它进⾏访问, 同时设置它的时间戳dfn[u]和low[u]为++ndfn_num,以及进栈.第⼆种: 访问过的,并且在栈中,我们直接更新我们当前节点的low[] --> 注意应该⽤low[u] 和 dfn[v]⽐较.第三种: 访问过的,并且不在栈中的, 我们直接跳过.因为这个时候,所以它已经染⾊了,属于⼀个连通块了.第⼆步: 如果dfn[u] == low[u] 说明已经找到⼀个连通块了.这时候我们要将栈顶元素弹出,直到当前节点. 记得也要修改inSt, 同时维护我们需要的信息.*/void Tarjan(int u) {int v, i;dfn[u] = low[u] = ++dfn_num; //添加时间戳.st[++top] = u; // 进栈inSt[u] = true; // 标⽰在栈for (i=head[u]; i; i=edge[i].lst) {v = edge[i].to;if (!dfn[v]) {Tarjan(v);low[u] = min(low[u], low[v]);} else if (inSt[v]) {low[u] = min(low[u], dfn[v]);}}if (dfn[u] == low[u]) {col_num++;do {inSt[st[top]] = false;col[st[top]] = col_num;size[col_num]++;} while (st[top--] != u);}}View Code加上2个板⼦题./problem/1332/题⽬很简单: 要你求出最⼤的强连通块,如果有多个则输出字典序最⼩的⼀个.#include <cstdio>#include <cstring>#include <algorithm>using namespace std;const int maxn = 5e4+500;struct Edge {int lst;int to;}edge[maxn<<1];int head[maxn];int qsz = 1;inline void add(int u, int v) {edge[qsz].lst = head[u];edge[qsz].to = v;head[u] = qsz++;}int dfn[maxn]; // 时间戳int dfn_num = 0; // 时间int low[maxn]; // 节点u所能访问到的最⼩时间戳int inSt[maxn]; // 节点u是否在栈中.int st[maxn];int top = 0;// 我们维护的信息.int col[maxn]; // 给节点染⾊, 同⼀个连通块的节点应该是同⼀个颜⾊的.int col_num = 0; // 颜⾊值.int size[maxn]; // 每个颜⾊值所拥有的块数.int id[maxn];void Tarjan(int u) {int v, i;dfn[u] = low[u] = ++dfn_num; //添加时间戳.st[++top] = u; // 进栈inSt[u] = true; // 标⽰在栈for (i=head[u]; i; i=edge[i].lst) {v = edge[i].to;if (!dfn[v]) {Tarjan(v);low[u] = min(low[u], low[v]);} else if (inSt[v]) {low[u] = min(low[u], dfn[v]);}}if (dfn[u] == low[u]) {col_num++;id[col_num] = u;do {inSt[st[top]] = false;col[st[top]] = col_num;size[col_num]++;id[col_num] = min(id[col_num], st[top]);} while (st[top--] != u);}}int main(){memset(id, 0x3f, sizeof(id));int n, i, u, v, m, t;scanf("%d%d", &n, &m);for (i=1; i<=m; ++i) {scanf("%d%d%d", &u, &v, &t);add(u, v);if (t==2) add(v, u);}for (i=1; i<=n; ++i)if (!dfn[i]) Tarjan(i);int mm = 0, tcol = -1;for (i=1; i<=col_num; ++i)if (mm < size[i]) {mm = size[i];tcol = i;} else if (m == size[i]) {if (id[tcol] > id[i])tcol = i;}// printf("%d \n", tcol);printf("%d\n", mm);for (i=1; i<=n; ++i)if (col[i] == tcol) printf("%d ", i);printf("\n");return0;}View Codehttps:///problem/HYSBZ-1051题⽬: 求出所有⽜都欢迎的⽜的个数. 我们可以把所有连通块求出,然后把⼀个连通块看成⼀个点,即缩点. 然后找到出度为零的点(连通块), 如果有且只有⼀个,那么连通块的点数就是答案,否则答案为零.#include <cstdio>#include <algorithm>using namespace std;struct Edge {int lst;int to;}edge[50500];int head[10100];int qsz = 1;inline void add(int u, int v) {edge[qsz].lst = head[u];edge[qsz].to = v;head[u] = qsz++;}int dfn[10100]; // 时间戳int dfn_num = 0; // 时间int low[10100]; // 节点u所能访问到的最⼩时间戳int inSt[10100]; // 节点u是否在栈中.int st[10100];int top = 0;// 我们维护的信息.int col[10100]; // 给节点染⾊, 同⼀个连通块的节点应该是同⼀个颜⾊的.int col_num = 0; // 颜⾊值.int size[10100]; // 每个颜⾊值所拥有的块数./*第⼀步: 访问当前节点的所有⼦节点: ⼦节点有三种第⼀种: 未访问过的, 我们对它进⾏访问, 同时设置它的时间戳dfn[u]和low[u]为++ndfn_num,以及进栈.第⼆种: 访问过的,并且在栈中,我们直接更新我们当前节点的low[] --> 注意应该⽤low[u] 和 dfn[v]⽐较. 第三种: 访问过的,并且不在栈中的, 我们直接跳过.因为这个时候,所以它已经染⾊了,属于⼀个连通块了. 第⼆步: 如果dfn[u] == low[u] 说明已经找到⼀个连通块了.这时候我们要将栈顶元素弹出,直到当前节点. 记得也要修改inSt, 同时维护我们需要的信息.*/void Tarjan(int u) {int v, i;dfn[u] = low[u] = ++dfn_num; //添加时间戳.st[++top] = u; // 进栈inSt[u] = true; // 标⽰在栈for (i=head[u]; i; i=edge[i].lst) {v = edge[i].to;if (!dfn[v]) {Tarjan(v);low[u] = min(low[u], low[v]);} else if (inSt[v]) {low[u] = min(low[u], dfn[v]);}}if (dfn[u] == low[u]) {col_num++;do {inSt[st[top]] = false;col[st[top]] = col_num;size[col_num]++;} while (st[top--] != u);}}bool ou[10010];int main(){// freopen("E:\\input.txt", "r", stdin);int n, i, j, u, v, m;scanf("%d%d", &n, &m);for (i=1; i<=m; ++i) {scanf("%d%d", &u, &v);add(u, v);}for (i=1; i<=n; ++i)if (!dfn[i])Tarjan(i);// 缩点操作int cnt = 0, res = 0;for (i=1; i<=n; ++i) {if (ou[col[i]]) continue;for (j=head[i]; j; j=edge[j].lst) {v = edge[j].to;if (col[i] != col[v]) {ou[col[i]] = true;break;}}}for (i=1; i<=col_num; ++i) {if (!ou[i]) {res = size[i];cnt++;}if (cnt > 1) {res = 0;break;}}printf("%d\n", res);return0;}View Code。
tarjan算法的原理

tarjan算法的原理Tarjan算法是一种用于图的深度优先搜索(DFS)的算法,它可以在线性时间内找到一个有向图中的所有强连通分量。
这个算法是由美国计算机科学家Robert Tarjan在1972年提出的,是解决图论中强连通分量问题的经典算法之一。
在理解Tarjan算法之前,我们先来了解一下什么是强连通分量。
在有向图中,如果对于任意两个顶点u和v,存在从u到v和从v到u 的路径,那么称这两个顶点是强连通的。
而强连通分量就是指图中的一组顶点,其中任意两个顶点都是强连通的,且不属于任何其他的强连通分量。
Tarjan算法的核心思想是利用DFS遍历图,并在遍历的过程中标记每个顶点的强连通分量。
下面我们来详细了解一下Tarjan算法的原理。
我们需要定义两个重要的数组:dfn数组和low数组。
dfn数组记录了每个顶点被访问的次序,low数组记录了每个顶点能够追溯到的最早的栈中的顶点的次序。
Tarjan算法的流程如下:1. 对图中的每个顶点进行遍历,如果该顶点还未被访问,则以该顶点开始进行DFS遍历。
2. 在DFS遍历的过程中,对于每个顶点v,首先将其标记为已访问,并将dfn[v]和low[v]都设置为当前的遍历次序。
3. 然后,遍历v的每个邻接顶点u,如果u还未被访问,则递归地对u进行DFS遍历。
4. 在递归返回的过程中,更新low[v]为min(low[v], low[u]),其中u是v的一个邻接顶点。
5. 最后,如果dfn[v]等于low[v],则将从v开始的连通分量中的所有顶点输出为一个强连通分量。
Tarjan算法通过维护一个栈来实现DFS的非递归遍历。
具体来说,当访问一个顶点v时,将v入栈,并将v标记为已访问。
然后,遍历v的每个邻接顶点u,如果u还未被访问,则递归地对u进行DFS 遍历。
在递归返回之后,判断low[u]是否小于low[v],如果是,则更新low[v]为low[u]。
最后,如果dfn[v]等于low[v],则将从v 开始的连通分量中的所有顶点出栈,并输出为一个强连通分量。
强连通分量

强连通分量【引言】本文讲述了求强连通分量的T arjan算法的思想以及一般过程,鉴于T arjan算法比较抽象,很多人明白怎么做但不理解为什么,所以本文以作者对T arjan算法的理解为主,供读者参考。
【正文】一、强连通分量的概念及性质。
概念:对于一个有向图G,存在点集P使得P中任意两点可以互相到达,则称P为一个强连通分量。
性质:如果对于一个点v,在v可以到达的点中,可以回到v的点和v一定属于同一个强连通分量;不可以回到v的点和v一定不属于一个强连通分量。
二、T arjan思想1.初步思想:利用搜索算法找到点v可以到达的所有点,再判断这些点中哪些可以回到v,并对符合条件的点进行染色,染成同色的点组成一个强连通分量。
2.深搜性质的利用:首先对点来做一些定义:白色点:表示该点还没有被访问过。
灰色点:表示正在处理该点发出的边,还没处理完。
黑色点:表示该点发出的所有边都处理完了。
深搜序:表示所有点由白变灰的顺序。
附属点:某点v从刚刚变成灰色到刚刚变成黑色这段时间内访问并处理过的所有点(不包括那些在v之前变灰或变黑的点,那些点在这段时间内只访问但没处理)性质1:从开始处理点v到处理完点v,所有的附属点都从白变黑。
性质2:在开始处理点v之前的那个时刻,所有的灰色点都可以到达点v,称这些灰色点为祖先点,并且这些祖先点构成一条链。
性质3:对于性质2所述的那些灰色点,在深搜序中靠前的点一定可以到达靠后的点。
这些性质有助于理解后面的思想。
3.引入进一步思想:首先是想法:对于当前点v,和它在同一个强连通分量里的点一定可以到达它。
这些点可以分为两部分:(1)性质2中那些灰色的点(祖先点) (2)其他可以到达v的点对于(2)那部分点,又可以分为两部分:和v在同一个强连通分量里的:这部分点将会变成v的附属点,所以不必考虑。
不和v在同一个强连通分量里的:这部分点显然不在考虑范畴内,但一会儿要用到。
所以,我们只需要考虑性质2中的祖先点!!!也就是说,v的附属点中,可以直接或间接到达那些祖先点的点,和v一定属于同一个强连通分量(通俗点说,就是v可以到它,它可以到 v的祖先,v的祖先又可以到v)。
Tarjan算法

[有向图强连通分量]在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected)。
如果有向图G的每两个顶点都强连通,称G是一个强连通图。
非强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components)。
下图中,子图{1,2,3,4}为一个强连通分量,因为顶点1,2,3,4两两可达。
{5},{6}也分别是两个强连通分量。
直接根据定义,用双向遍历取交集的方法求强连通分量,时间复杂度为O(N^2+M)。
更好的方法是Kosaraju算法或Tarjan算法,两者的时间复杂度都是O(N+M)。
本文介绍的是Tarjan算法。
[Tarjan算法]Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。
搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。
定义DFN(u)为节点u搜索的次序编号(时间戳),Low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号。
由定义可以得出,Low(u)=Min{DFN(u),Low(v),(u,v)为树枝边,u为v的父节点DFN(v),(u,v)为指向栈中节点的后向边(非横叉边)}当DFN(u)=Low(u)时,以u为根的搜索子树上所有节点是一个强连通分量。
算法伪代码如下tarjan(u){DFN[u]=Low[u]=++Index // 为节点u设定次序编号和Low初值Stack.push(u)// 将节点u压入栈中for each (u, v) in E // 枚举每一条边if(v is not visted)// 如果节点v未被访问过tarjan(v)// 继续向下找Low[u]= min(Low[u], Low[v])else if(v in S)// 如果节点u还在栈内Low[u]= min(Low[u], DFN[v])if(DFN[u]== Low[u])// 如果节点u是强连通分量的根repeatv = S.pop// 将v退栈,为该强连通分量中一个顶点print vuntil (u== v)}接下来是对算法流程的演示。
2-sat问题的tarjan算法

2-sat问题是一种布尔可满足性问题,即判断一个由布尔变量和它们的逻辑运算构成的合取范式是否存在可满足的赋值。
在计算机科学和逻辑学中,2-sat问题具有重要的理论和实际意义。
为了解决2-sat问题,人们提出了许多有效的算法,其中tarjan算法是一种经典且高效的解决方法。
1. tarjan算法的概述tarjan算法是由美国的计算机科学家Robert Tarjan在1972年提出的,它主要用于解决有向图中的强连通分量问题。
在2-sat问题中,可以将布尔变量和它们的逻辑运算构成的合取范式转化为一个有向图。
然后利用tarjan算法来求解图中的强连通分量,从而判断2-sat问题是否可满足。
2. tarjan算法的原理tarjan算法的核心是利用深度优先搜索(DFS)来遍历图中的节点,并且通过维护一个栈来记录搜索路径上的节点。
在DFS的过程中,通过比较节点的深度和搜索路径上的节点的深度来判断是否存在环路,从而找到强连通分量。
利用tarjan算法求解2-sat问题的关键在于将逻辑运算转化为有向图,同时构建出正确的搜索路径和深度信息,以便进行强连通分量的判断。
3. tarjan算法的优势与其他算法相比,tarjan算法具有许多优势。
tarjan算法的时间复杂度为O(V+E),其中V为图中的节点数,E为图中的边数。
这意味着即使在大规模的图中,tarjan算法也能够在合理的时间内得到结果。
tarjan算法的实现相对比较简单,只需要进行一次DFS遍历和一些基本的数据结构操作即可完成。
另外,tarjan算法的结果也比较容易理解和解释,对于2-sat问题的求解具有很好的可解释性。
4. tarjan算法的应用由于tarjan算法在解决2-sat问题中具有较高的效率和可靠性,因此它在实际的计算机科学和工程领域得到了广泛的应用。
在编译原理中,可以利用tarjan算法进行程序的静态分析和优化;在人工智能和图像处理中,可以利用tarjan算法对逻辑规则进行推理和推导;在电路设计和布线规划中,也可以利用tarjan算法对逻辑电路进行布线和优化。
连通分量——精选推荐

连通分量图论连通分量0.1 概述图论中,或者説OI中研究的连通分量主要有三种:强连通分量、点双连通分量、边双连通分量。
本⽂并不打算着重讲述tarjan算法的具体实现,默认读者已掌据此前置知识。
1.1 强连通分量强连通分量是指:在有向图中,强连通分量中的任意两点(u,v),存在u→v和v→u的两条不同路径。
极⼤强连通分量:⽆法继续扩张的强连通分量。
在通常语境下,⼀般默认“强连通分量”指的就是"极⼤强连通分量"算法实现:tarjan算法可以做到O(n)效率地求解。
1.2 例题:[USACO15JAN]草鉴定Grass Cownoisseur可以发现,将图缩点染⾊后,⼀个分量内的点之间可以任意到达。
即:答案的最⼩值等于1号草场所处分量的⼤⼩。
在已经缩点后的图上计算由1号草场所在分量向其余分量求最长路dis1,再求由其余分量向1号草场所在分量的最长路dis2(建反边)对于原图上存在的⼀条边u→v,如果这两个点同处⼀个连通分量上,在这条边上逆⾏⼀次是没有任何意义的;但是如果这两个点并不在⼀个连能分量上,那么可以:先⾛到v所在的分量dis1[v分量],再逆⾏回到u分量,然后回到1号草场分量dis2[u分量],最后逛完1号分量siz[1号分量]。
因此:ans=max(ans,dis1[v]+dis2[u]+size[1分量]2.1 点双连通分量点双连通分量指:⽆向图中,分量中的任意两点间存在经过的点完全不相同的两条路径。
2.2 性质割点起到类似“分割”的作⽤,分割两个点双连通分量。
⼀个割点最多出现在两个点双连通分量上,⽽⾮割点则最多出现在⼀个点双连通分量上。
注意到这⼀条性质后,其实点双连通分量的求解就不是⼀个难事了。
⼀个点双连通分量最早会在割点或者图的根上被探测到。
当tarjan找到⼀个割点后,将栈中的所有点和这个割点归纳到⼀个点双连通分量中。
栈中的所有点弹出,但这个割点不弹出。
void tarjan(int u){DFN[u]=LOW[u]=++DFN[0]; sta[++top]=u; in[u]=true;int cnt=0;tor(i,u){int v=edge[i];if(!DFN[v]){cnt++;tarjan(v);LOW[u]=min(LOW[u],LOW[v]);if(LOW[v]>=DFN[u]){tot++;if(u!=root) ge[u]=true;while(sta[top]!=u){in[sta[top]]=false;lis[tot].push_back(sta[top]);top--;}lis[tot].push_back(u);}}else if(in[v]) LOW[u]=min(LOW[u],DFN[v]);}if(u==root&&cnt>=2) ge[u]=true;}2.3 例题:[HNOI2012]矿场搭建对图做⼀次点双连通分量的归纳,分情况讨论。
图论常见模型

例题分析
若图中有环…… 在同一SCC里的全部点的“连通性”是等 价的:能够到达它们的点集是相同的,从 它们出发能够到达的点集也是相同的。
若从此SCC买入,则一定买价格最低的点 若从此SCC卖出,则一定买价格最高的点
19
例题分析
最终解法:
对原图求SCC并缩点,设新点i中最高价格点 为H[i],最低价格点为L[i]。在新的DAG图上 有DP: dp[i] = max(H[i], max{dp[j], 当ij有边时}) 最终结果为max{dp[i] – L[i]} 注意:应排除不能到达N的点
31
例题分析
Sample Input
12 0 1 1 10 1 5 10
Sample Output 2
32
例题分析
Floyd预处理所有点对间最短路径dis[][]
设有任务a,b,如果在完成任务a后还来得 及走到b处继续,即deadline[a] + cost + dis[a][b] <= deadline[b],则连一条有向边 (a,b)…
对于X中任意两元素a,b,若有a≤b或b≤a, 则称a和b是可比的,否则为不可比。
37
Dilworth定理
一个链C是X的一个子集,它的任意两个元 素都可比。 一个反链A是X的一个子集,它的任意两个 元素都不能进行比较。 Dilworth定理: 最大反链的大小 = 最少可 划分成的链的数目
38
41
Dilworth定理
(a是b的倍数)这是一个偏序关系,则此题要 求的即为最大反链的大小 此题中反链难以直接计算,利用Dilworth定 理,等价于计算最少可划分为的链数 于是我们回到最小路径覆盖,用二分匹配 求解即可
强连通分量的三种算法

有向图中, u可达v不一定意味着v可达u. 相互可达则属于同一个强连通分量(S trongly Connected Component, SCC)最关键通用部分:强连通分量一定是图的深搜树的一个子树。
一、Kosaraju算法1. 算法思路基本思路:这个算法可以说是最容易理解,最通用的算法,其比较关键的部分是同时应用了原图G和反图GT。
(步骤1)先用对原图G进行深搜形成森林(树),(步骤2)然后任选一棵树对其进行深搜(注意这次深搜节点A能往子节点B走的要求是EAB存在于反图GT),能遍历到的顶点就是一个强连通分量。
余下部分和原来的森林一起组成一个新的森林,继续步骤2直到没有顶点为止。
改进思路:当然,基本思路实现起来是比较麻烦的(因为步骤2每次对一棵树进行深搜时,可能深搜到其他树上去,这是不允许的,强连通分量只能存在单棵树中(由开篇第一句话可知)),我们当然不这么做,我们可以巧妙的选择第二深搜选择的树的顺序,使其不可能深搜到其他树上去。
想象一下,如果步骤2是从森林里选择树,那么哪个树是不连通(对于GT来说)到其他树上的呢?就是最后遍历出来的树,它的根节点在步骤1的遍历中离开时间最晚,而且可知它也是该树中离开时间最晚的那个节点。
这给我们提供了很好的选择,在第一次深搜遍历时,记录时间i离开的顶点j,即numb[i] =j。
那么,我们每次只需找到没有找过的顶点中具有最晚离开时间的顶点直接深搜(对于GT来说)就可以了。
每次深搜都得到一个强连通分量。
隐藏性质:分析到这里,我们已经知道怎么求强连通分量了。
但是,大家有没有注意到我们在第二次深搜选择树的顺序有一个特点呢?如果在看上述思路的时候,你的脑子在思考,相信你已经知道了!!!它就是:如果我们把求出来的每个强连通分量收缩成一个点,并且用求出每个强连通分量的顺序来标记收缩后的节点,那么这个顺序其实就是强连通分量收缩成点后形成的有向无环图的拓扑序列。
为什么呢?首先,应该明确搜索后的图一定是有向无环图呢?废话,如果还有环,那么环上的顶点对应的所有原来图上的顶点构成一个强连通分量,而不是构成环上那么多点对应的独自的强连通分量了。
连通分量(tarjan算法)

连通分量(tarjan算法)对于有向图中,连通分量叫强连通分量对于⽆向图中,连通分量叫双连通分量,⽽在双连通分量中,⼜分为点双连通和边双连通。
重点讨论双连通的情况:以割点区分连通情况的双连通叫做点双连通分量,以割边区分连通情况的双连通叫做边双连通分量。
⽐如这个图中:1 4| \ / \| \ / \| 2 5| / \ /| / \ /3 6如果要的到两个连通分量的话,我们只有以2节点为割点,将图分为点双连通。
我们便可以得到两个双连通分量(1,2,3)、(2,4,5,6)。
再⽐如这个图:1 5| \ / \| \ / \| 2 — 4 6| / \ /| / \ /3 7如果要得到两个连通分量的话,我们只能以(2,4)边为割边,得到两个边双连通分量。
具体是将⼀个⽆向图分成点双连通还是边双连通得看具体的问题。
下⾯具体讨论⼀下⽤tarjan算法实现起来的区别。
1、对于边双连通分量,由于是以边为连通间的区分,那么每个点只属于⼀个连通分量,我们在深度遍历的时候,只需要记录某⼀点是否被访问即可。
2、但是对于点双连通分量,由上图也可以看出,2节点同时属于两个连通分量,如果还是以节点为重判对象的话,将得到错误的答案。
此时,我们应该以边为重判对象,⽽该边的两个端点,都同属于⼀个点双连通。
对于边双连通的实现,由于没有节点会重复属于两个以上的连通分量,所以我们可以先简单的遍历⼀遍整个图的tarjan标号(low、dnf)。
之后再进⾏⼀次深度遍历,以low[v]>dnf[u]为换⾊标准染⾊,最后每种颜⾊即⼀个分量。
代码:void tarjan(int u,int f) //f为⽗节点{low[u]=dnf[u]=++count;visit[u]=1;S.push(u);for(node *p=link[u];p;p=p->next){if(p->v==f)continue;if(!visit[p->v]) //⼀节点为重判对象{tarjan(p->v,u);if(low[u]>low[p->v]){low[u]=low[p->v];}}else if(low[u]>dnf[p->v])low[u]=dnf[p->v];}}void set_color(int u){for(node *p=link[u];p;p=p->next){if(!color[p->v]){if(low[p->v]>dnf[u]) //找到了关键割边,换⼀种颜⾊染⾊color[p->v]=++cut;elsecolor[p->v]=color[u]; //保持原来的颜⾊set_color(p->v);}}}对于点双连通的实现,经过上⾯的分析,由于存在点既属于A分量⼜属于B分量,所有我们此时采纳边为重判对象,⽽该边的两端点同属于⼀个分量代码:void col(int u){int x;node *p;++cut;do{p=stack[--top];color[p->v]=color[p->op->v]=cut;}while(p->op->v!=u);}void tarjan(int u){low[u]=dnf[u]=++cut;for(node *p=link[u];p;p=p->next){if(p->vist) //该边已被访问continue;p->vist=p->op->vist=1; //两个⽅向都标记已访问stack[top++]=p; //该边进栈if(!dnf[p->v]){tarjan(p->v);if(low[u]>low[p->v])low[u]=low[p->v];if(low[p->v]>=dnf[u]) //找到了关键割点col(u);}else if(low[u]>dnf[p->v])low[u]=dnf[p->v];}}。
强连通分量

结论 • 这让我们就将简化版问题给解决了~
• 首先,统计出图中出度为0的点。
• 如果这样的点只有一个,那么从该点 出发反向dfs遍历图。如果可以遍历 所有点,那么说明这个点就是题目要 求的点。
• 时间复杂度o(n+e)
不要高兴的太早
• 上面的结论在有环图是否适用呢?
• 很可惜,结论不成立。 • 因为大牛可以互相仰慕,所以若最受欢迎 的牛与其他牛互相仰慕,这些牛都是最受 欢迎的牛。这样,满足条件的点出度可能 不为0,也不止有一个。
Kosaraju算法(黑书286)
• 算法步骤(正确性证明略)
– 对有向图进行DFS,记录下顶点变黑的时间 A[i]。
– 改变图 的每一条边的方向,生成新图GT。
– 按上次DFS 顶点变黑的时间A[i]由大到小顺 序对GT进行DFS。遍历结果构成森林W 。
– W 中每棵树的结点构成了有向图的一个 强连通分量。
G(6) E(2)
• 得到A[i]从大 到小的顺序: •FGACDEB
D(3)
演示
• 改变图G 的每一条边 的方向,生成新图GT • 按A[i]由大到小顺序
A (5)
C(4) B(1) F(7)
FGACDEB
对GT进行第二次DFS
G(6) E(2)
• 得到森林w
• FG||ACB||DE
D(3)
演示
• 关于缩点法的进一步深入讨论,请参考周 源2005年信息学奥赛冬令营论文。
部分用到强连通分量的题目
• POJ 2186 Popular Cows (基础) • POJ 2553 The Bottom of a Graph (alpc OJ 1274)(基础) • POJ 1236 Network of Schools (基础) • 2010中南赛 light sources(alpc OJ) (基础) • POJ 2762 Going from u to v or from v to u? (中等,弱连通分量 ) • POJ 3160 Father Christmas flymouse(难,DP题) • POJ 1904 King‘s Quest (难,推荐,非缩点,匹配思想与强连通分量的转化)
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
有向图的强连通分量分类:C/C++程序设计2009-04-15 16:50 2341人阅读评论(1) 收藏举报最关键通用部分:强连通分量一定是图的深搜树的一个子树。
一、Kosaraju算法1.算法思路基本思路:这个算法可以说是最容易理解,最通用的算法,其比较关键的部分是同时应用了原图G和反图G T。
(步骤1)先用对原图G进行深搜形成森林(树),(步骤2)然后任选一棵树对其进行深搜(注意这次深搜节点A能往子节点B走的要求是E AB存在于反图G T),能遍历到的顶点就是一个强连通分量。
余下部分和原来的森林一起组成一个新的森林,继续步骤2直到没有顶点为止。
7改进思路:当然,基本思路实现起来是比较麻烦的(因为步骤2每次对一棵树进行深搜时,可能深搜到其他树上去,这是不允许的,强连通分量只能存在单棵树中(由开篇第一句话可知)),我们当然不这么做,我们可以巧妙的选择第二深搜选择的树的顺序,使其不可能深搜到其他树上去。
想象一下,如果步骤2是从森林里选择树,那么哪个树是不连通(对于G T来说)到其他树上的呢?就是最后遍历出来的树,它的根节点在步骤1的遍历中离开时间最晚,而且可知它也是该树中离开时间最晚的那个节点。
这给我们提供了很好的选择,在第一次深搜遍历时,记录时间i离开的顶点j,即numb[i]=j。
那么,我们每次只需找到没有找过的顶点中具有最晚离开时间的顶点直接深搜(对于G T来说)就可以了。
每次深搜都得到一个强连通分量。
隐藏性质:分析到这里,我们已经知道怎么求强连通分量了。
但是,大家有没有注意到我们在第二次深搜选择树的顺序有一个特点呢?如果在看上述思路的时候,你的脑子在思考,相信你已经知道了!!!它就是:如果我们把求出来的每个强连通分量收缩成一个点,并且用求出每个强连通分量的顺序来标记收缩后的节点,那么这个顺序其实就是强连通分量收缩成点后形成的有向无环图的拓扑序列。
为什么呢?首先,应该明确搜索后的图一定是有向无环图呢?废话,如果还有环,那么环上的顶点对应的所有原来图上的顶点构成一个强连通分量,而不是构成环上那么多点对应的独自的强连通分量了。
然后就是为什么是拓扑序列,我们在改进分析的时候,不是先选的树不会连通到其他树上(对于反图GT来说),也就是后选的树没有连通到先选的树,也即先出现的强连通分量收缩的点只能指向后出现的强连通分量收缩的点。
那么拓扑序列不是理所当然的吗?这就是Kosaraju算法的一个隐藏性质。
2.伪代码Kosaraju_Algorithm:step1:对原图G进行深度优先遍历,记录每个节点的离开时间。
step2:选择具有最晚离开时间的顶点,对反图GT进行遍历,删除能够遍历到的顶点,这些顶点构成一个强连通分量。
step3:如果还有顶点没有删除,继续step2,否则算法结束。
3.实现代码:#include<iostream>using namespace std;const int MAXN = 110;typedef int AdjTable[MAXN]; //邻接表类型int n;bool flag[MAXN]; //访问标志数组int belg[MAXN]; //存储强连通分量,其中belg[i]表示顶点i属于第belg[i]个强连通分量int numb[MAXN]; //结束时间标记,其中numb[i]表示离开时间为i的顶点AdjTable adj[MAXN], radj[MAXN]; //邻接表,逆邻接表//用于第一次深搜,求得numb[1..n]的值void VisitOne(int cur, int &sig){flag[cur] = true;for ( int i=1; i<=adj[cur][0]; ++i ){if ( false==flag[adj[cur][i]] ){VisitOne(adj[cur][i],sig);}}numb[++sig] = cur;}//用于第二次深搜,求得belg[1..n]的值void VisitTwo(int cur, int sig){flag[cur] = true;belg[cur] = sig;for ( int i=1; i<=radj[cur][0]; ++i ){if ( false==flag[radj[cur][i]] ){VisitTwo(radj[cur][i],sig);}}}//Kosaraju算法,返回为强连通分量个数int Kosaraju_StronglyConnectedComponent() {int i, sig;//第一次深搜memset(flag+1,0,sizeof(bool)*n);for ( sig=0,i=1; i<=n; ++i ){if ( false==flag[i] ){VisitOne(i,sig);}}//第二次深搜memset(flag+1,0,sizeof(bool)*n);for ( sig=0,i=n; i>0; --i ){if ( false==flag[numb[i]] ){VisitTwo(numb[i],++sig);}}return sig;}二、Trajan算法1.算法思路:这个算法思路不难理解,由开篇第一句话可知,任何一个强连通分量,必定是对原图的深度优先搜索树的子树。
那么其实,我们只要确定每个强连通分量的子树的根,然后根据这些根从树的最低层开始,一个一个的拿出强连通分量即可。
那么身下的问题就只剩下如何确定强连通分量的根和如何从最低层开始拿出强连通分量了。
那么如何确定强连通分量的根,在这里我们维护两个数组,一个是indx[1..n],一个是mlik[1..n],其中indx[i]表示顶点i开始访问时间,mlik[i]为与顶点i邻接的顶点未删除顶点j的mlik[j]和mlik[i]的最小值(mlik[i]初始化为indx[i])。
这样,在一次深搜的回溯过程中,如果发现mlik[i]==indx[i]那么,当前顶点就是一个强连通分量的根,为什么呢?因为如果它不是强连通分量的跟,那么它一定是属于另一个强连通分量,而且它的根是当前顶点的祖宗,那么存在包含当前顶点的到其祖宗的回路,可知mlik[i]一定被更改为一个比indx[i]更小的值。
至于如何拿出强连通分量,这个其实很简单,如果当前节点为一个强连通分量的根,那么它的强连通分量一定是以该根为根节点的(剩下节点)子树。
在深度优先遍历的时候维护一个堆栈,每次访问一个新节点,就压入堆栈。
现在知道如何拿出了强连通分量了吧?是的,因为这个强连通分量时最先被压人堆栈的,那么当前节点以后压入堆栈的并且仍在堆栈中的节点都属于这个强连通分量。
当然有人会问真的吗?假设在当前节点压入堆栈以后压入并且还存在,同时它不属于该强连通分量,那么它一定属于另一个强连通分量,但当前节点是它的根的祖宗,那么这个强连通分量应该在此之前已经被拿出。
现在没有疑问了吧,那么算法介绍就完了。
2.伪代码:Tarjan_Algorithm:step1:找一个没有被访问过的节点v,goto step2(v)。
否则,算法结束。
step2(v):初始化indx[v]和mlik[v]对于v所有的邻接顶点u:1)如果没有访问过,则step2(u),同时维护mlik[v]2)如果访问过,但没有删除,维护mlik[v]如果indx[v]==mlik[v],那么输出相应的强连通分量3.实现代码#include<iostream>using namespace std;const int MAXN = 110;const char NOTVIS = 0x00; //顶点没有访问过的状态const char VIS = 0x01; //顶点访问过,但没有删除的状态const char OVER = 0x02; //顶点删除的状态typedef int AdjTable[MAXN]; //邻接表类型int n;char flag[MAXN]; //用于标记顶点状态,状态有NOTVIS,VIS,OVERint belg[MAXN]; //存储强连通分量,其中belg[i]表示顶点i属于第belg[i]个强连通分量int stck[MAXN]; //堆栈,辅助作用int mlik[MAXN]; //很关键,与其邻接但未删除顶点地最小访问时间int indx[MAXN]; //顶点访问时间AdjTable adj[MAXN]; //邻接表//深搜过程,该算法的主体都在这里void Visit(int cur, int &sig, int &scc_num){int i;stck[++stck[0]] = cur; flag[cur] = VIS;mlik[cur] = indx[cur] = ++sig;for ( i=1; i<=adj[cur][0]; ++i ){if ( NOTVIS==flag[adj[cur][i]] ){Visit(adj[cur][i],sig,scc_num);if ( mlik[cur]>mlik[adj[cur][i]] ){mlik[cur] = mlik[adj[cur][i]];}}else if ( VIS==flag[adj[cur][i]] ){if ( mlik[cur]>indx[adj[cur][i]] ) //该部分的indx 应该是mlik,但是根据算法的属性,使用indx也可以,且时间更少{mlik[cur] = indx[adj[cur][i]];}}}if ( mlik[cur]==indx[cur] ){++ scc_num;do{belg[stck[stck[0]]] = scc_num;flag[stck[stck[0]]] = OVER;}while ( stck[stck[0]--]!=cur );}}//Tarjan算法,求解belg[1..n],且返回强连通分量个数,int Tarjan_StronglyConnectedComponent(){int i, sig, scc_num;memset(flag+1,NOTVIS,sizeof(char)*n);sig = 0; scc_num = 0; stck[0] = 0;for ( i=1; i<=n; ++i ){if ( NOTVIS==flag[i] ){Visit(i,sig,scc_num);}}return scc_num;}三、Gabow算法1.思路分析这个算法其实就是Tarjan算法的变异体,我们观察一下,只是它用第二个堆栈来辅助求出强连通分量的根,而不是Tarjan算法里面的indx[]和mlik[]数组。