求强连通分量的几种算法的实现与分析
连通图的割点、割边(桥)、块、缩点,有向图的强连通分量
连通图的割点、割边(桥)、块、缩点,有向图的强连通分量一、基本概念无向图割点:删掉它之后(删掉所有跟它相连的边),图必然会分裂成两个或两个以上的子图。
块:没有割点的连通子图割边:删掉一条边后,图必然会分裂成两个或两个以上的子图,又称桥。
缩点:把没有割边的连通子图缩为一个点,此时满足任意两点间都有两条路径相互可达。
求块跟求缩点非常相似,很容易搞混,但本质上完全不同。
割点可以存在多个块中(假如存在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之前也早就提前出栈了!画一个图照着代码模拟一下可以方便理解。
图连通性算法及应用
图连通性算法及应用图是计算机科学领域中常见的数据结构,用于表示对象之间的关系。
在图论中,图的连通性是一个重要的概念,指的是在图中任意两个顶点之间是否存在路径。
图连通性算法是为了判断图中的连通性而设计的算法,并且在实际应用中有着广泛的应用。
一、连通性的定义与分类在图论中,连通性有两种常见的定义方式:强连通性和弱连通性。
强连通性是指在有向图中,任意两个顶点之间存在互相可达的路径;弱连通性是指在有向图中,将其所有有向边的方向忽略后,剩下的无向图是连通的。
本文将重点介绍无向图的连通性算法及其应用。
二、连通性算法的原理1. 深度优先搜索(DFS)深度优先搜索是最常用的连通性算法之一。
它从图中的一个顶点开始,沿着一条未访问过的边深入图中的下一个顶点,直到无法深入为止,然后回溯至上一个顶点,继续深入其他未访问过的顶点。
通过深度优先搜索算法,我们可以得到一个图的连通分量,从而判断图是否连通。
2. 广度优先搜索(BFS)广度优先搜索同样是常用的连通性算法之一。
它从图中的一个顶点开始,沿着一条未访问过的边遍历与该顶点直接相邻的所有顶点,然后再以这些相邻顶点为起点,继续遍历它们的相邻顶点,直到遍历完所有连通的顶点。
通过广度优先搜索算法,我们可以得到一个图的层次遍历树,从而判断图是否连通。
三、连通性算法的应用1. 社交网络分析在社交网络分析中,连通性算法可以用来判断一个社交网络中是否存在分割成多个互不相连的社群。
通过判断社交网络的连通性,我们可以发现隐藏在社交网络背后的关系网络,从而更好地理解和分析社会关系。
2. 网络路由优化在计算机网络中,连通性算法可以用来判断网络节点之间的连通性。
通过分析网络的拓扑结构,我们可以选择合适的路由算法,从而实现快速且可靠的数据传输。
3. 图像分割在计算机视觉和图像处理中,连通性算法可以用来判断图像中的连通区域。
通过判断图像的连通性,我们可以对图像进行分割和提取,从而实现目标检测和图像识别等应用。
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 的强联通分量。
连通分量算法
连通分量算法
连通分量算法是一种用于图像处理和图像分析的算法,主要用于找到图像中的连通区域。
在图像处理中,图像可以被看作是由一个个像素点组成的,而连通区域则是由相邻的像素点组成的区域。
连通分量算法通过扫描图像中的每一个像素,来找到图像中的所有连通区域。
在扫描过程中,如果两个像素点是相邻的且具有相同的像素值,则它们属于同一个连通区域。
连通分量算法可以用来进行图像分割,即将图像分成不同的部分,每一部分都代表一个连通区域。
这对于图像分析和识别任务非常重要,因为它可以帮助我们把图像中的不同物体区分开来。
在实际应用中,连通分量算法有多种实现方式,如基于深度优先搜索、基于广度优先搜索、基于并查集等。
每一种实现方式都有其特点和优缺点,需要根据具体应用场景进行选择。
总之,连通分量算法是图像处理和图像分析中的重要算法之一,它的应用范围非常广泛,可以帮助我们更好地理解和分析图像数据。
- 1 -。
强连通分量与模拟链表
强联通分量与模拟链表作者:逸水之寒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(V,E)中,在深度搜索时为每一个节点记录两个时间戳,分别是开始扫描的时间d和将其所有子节点全部扫描完的时间f;定义d(U)为节点集U中d的最小值,定义f(U)为节点集U中f的最大值。
基本步骤:1.对图G进行深度优先搜索,记录每个节点的d,f;2.求图G的转置Gt(所有节点不变,边的方向变反);3.按照步骤一所求的节点的f,按照降序,对Gt进行深度优先搜索,得到的深度优先森林,森林中深度为1所形成的每个树,即为各个强连通分量具体代码:import java.util.ArrayList;import java.util.LinkedList;import java.util.Scanner;/**** @author Founder* 通过深度优先搜索,查找强连通分量*/publicclass Main{staticint time = 0; //时间戳static ArrayList<Integer> topology; //记录第一次搜索结果的拓扑排序publicstaticvoid main(String[] args){/*** 输入方式:* 第一行输入节点的个数n* 后面n行输入第n个节点(从0开始数)链接的子节点,没有子节点则直接换行*/Scanner input = new Scanner(System.in);int n = input.nextInt();Node[] nodes = new Node[n];topology = new ArrayList<>();for(int i = 0; i< n; ++i){nodes[i] = new Node();}input.nextLine();for(int i = 0; i< n; ++i){String line = input.nextLine();if(!line.equals("")){String[] tempIntStr = line.split(" ");for(int j = 0; j <tempIntStr.length; ++j){nodes[i].addLinkNodes(Integer.parseInt(tempIntStr[j])); }}}dfs(nodes);/*** 准备第二次深度优先搜索,先构造转置图*/Node[] secondNodes = new Node[n];for(int i = 0; i< n; ++i){secondNodes[i] = new Node();}for(int m = 0; m < n; ++m){LinkedList<Integer>linkNodes = nodes[m].getLinkNodes(); for(int q = 0; q <linkNodes.size(); ++q){secondNodes[linkNodes.get(q)].addLinkNodes(m);}}/*** 开始第二次搜索*/secondDfs(secondNodes);}publicstaticvoid dfs(Node[] nodes){for(int i = 0; i<nodes.length; ++i){if(nodes[i].getColor() == Node.WHITE)dfsVisit(nodes,i);}}/*** 主要完成两个工作:设置颜色,设置时间戳* 附加工作:记录拓扑排序数组* @param nodes* @param no*/publicstaticvoid dfsVisit(Node[] nodes,int no){time++;nodes[no].setColor(Node.GRAY);nodes[no].setD(time);LinkedList<Integer>linkNodes = nodes[no].getLinkNodes(); for(int i = 0; i<linkNodes.size(); ++i){Node temp = nodes[linkNodes.get(i)];if(temp.getColor() == Node.WHITE){temp.setParent(nodes[no]);dfsVisit(nodes,linkNodes.get(i));}}nodes[no].setColor(Node.BLACK);topology.add(no);time++;nodes[no].setF(time);}/*** 与第一次的dfs基本相同,唯一的不同是遍历顺序按照拓扑排序进行 * @param nodes 图G的节点数组*/publicstaticvoid secondDfs(Node[] nodes){for(int i = topology.size() - 1; i>= 0; --i){if(nodes[topology.get(i)].getColor() == Node.WHITE) secondDfsVisit(nodes,topology.get(i));}}/*** 与第一次dfsVisit基本相同,具体有“两增两减”,不同点如下:* 1.增加输出当前结点编号* 2.不再记录时间戳(因为后面不会再用到这些数据)* 3.不再记录拓扑排序(因为后面不会再用到这些数据)* 4.回到优先搜索森林深度为1的节点(即没有设置父节点的节点)时,输出换行,代表一个强连通分量的结束* @param nodes 图G的节点数组* @param no 当前父结点*/publicstaticvoid secondDfsVisit(Node[] nodes,int no){ System.out.print(no + " ");nodes[no].setColor(Node.GRAY);LinkedList<Integer>linkNodes = nodes[no].getLinkNodes(); for(int i = 0; i<linkNodes.size(); ++i){Node temp = nodes[linkNodes.get(i)];if(temp.getColor() == Node.WHITE){temp.setParent(nodes[no]);secondDfsVisit(nodes,linkNodes.get(i));}}nodes[no].setColor(Node.BLACK);if(nodes[no].getParent() == null){System.out.println();}}}class Node{publicstaticfinalint WHITE = 0; publicstaticfinalint GRAY = 1; publicstaticfinalint BLACK = 2;privateint color = WHITE;privateint d = 0;privateint f = 0;private Node parent = null;private LinkedList<Integer>linkNodes = null;public Node(){linkNodes = new LinkedList<>();}publicint getColor() {return color;}publicvoid setColor(int color) {this.color = color;}publicint getD() {return d;}publicvoid setD(int d) {this.d = d;}publicint getF() {return f;}publicvoid setF(int f) {this.f = f;}public Node getParent() {return parent;}publicvoid setParent(Node parent) {this.parent = parent;}public LinkedList<Integer>getLinkNodes() {return linkNodes;}publicvoid setLinkNodes(LinkedList<Integer>linkNodes) { this.linkNodes = linkNodes;}publicvoid addLinkNodes(int no){linkNodes.add(no);}}基本原理:整个算法围绕着一个核心思想求解:对于图G总是从f(U)大的强连通分量指向f(U)小的强连通分量,对于G的转置Gt,则总是根据第一步算出来的f(U),从f(U)小的强连通分量指向f(U)大的强连通分量(这里可以这样理解,因为强连通是双向可达,所以转置对于强连通内部没有影响,对强连通分量之间,则指向关系发生了反转)。
强连通分量个数的最小值
强连通分量个数的最小值1. 引言在图论中,强连通分量是指图中的一组顶点,其中任意两个顶点都存在一条有向路径。
强连通分量个数的最小值是指在一个有向图中,最少需要将多少个顶点组成一个强连通分量。
本文将介绍强连通分量的概念、计算方法以及如何求解强连通分量个数的最小值。
2. 强连通分量的定义在有向图中,如果从顶点A到顶点B存在一条有向路径,同时从顶点B到顶点A也存在一条有向路径,则称顶点A和顶点B是强连通的。
如果一个有向图中的每个顶点都与其他所有顶点强连通,则该有向图被称为强连通图。
而强连通分量则是指有向图中的一组顶点,其中任意两个顶点都是强连通的,且不与其他顶点强连通。
3. 强连通分量的计算方法为了计算一个有向图的强连通分量,可以使用强连通分量算法,其中最常用的是Tarjan算法和Kosaraju算法。
3.1 Tarjan算法Tarjan算法是一种深度优先搜索算法,用于寻找有向图的强连通分量。
算法的基本思想是通过DFS遍历图中的每个顶点,并记录每个顶点的遍历次序和能够到达的最小顶点次序。
通过这些信息,可以判断顶点是否属于同一个强连通分量。
具体步骤如下:1.初始化一个空栈和一个空的遍历次序数组。
2.对于每个未遍历的顶点,进行深度优先搜索。
3.搜索过程中,记录每个顶点的遍历次序和能够到达的最小顶点次序,并将顶点加入栈中。
4.当搜索完成后,根据遍历次序和能够到达的最小顶点次序,可以确定每个顶点所属的强连通分量。
3.2 Kosaraju算法Kosaraju算法是另一种用于计算有向图强连通分量的算法。
算法的基本思想是通过两次深度优先搜索来确定强连通分量。
具体步骤如下:1.对原始图进行一次深度优先搜索,记录顶点的遍历次序。
2.对原始图的转置图(即将所有边的方向反转)进行一次深度优先搜索,按照遍历次序对顶点进行访问。
3.访问过程中,可以确定每个顶点所属的强连通分量。
4. 求解强连通分量个数的最小值要求解强连通分量个数的最小值,可以使用以下方法:1.使用Tarjan算法或Kosaraju算法计算有向图的强连通分量。
tarjan算法的原理
tarjan算法的原理Tarjan算法原理及应用一、引言Tarjan算法是一种用于图的深度优先搜索的算法,它可以在无向图或有向图中找到所有强连通分量。
这个算法由美国计算机科学家Robert Tarjan于1972年提出,被广泛应用于图论和算法领域。
本文将介绍Tarjan算法的原理及其应用。
二、Tarjan算法原理1. 深度优先搜索Tarjan算法是基于深度优先搜索的,深度优先搜索是一种图遍历算法,从一个顶点出发,沿着一条路径一直往下走,直到不能再走为止,然后回溯到前一个顶点,继续向未走过的路径探索。
这种搜索方式可以用递归或栈来实现。
2. 强连通分量在图中,如果任意两个顶点之间都存在路径,那么它们构成一个强连通分量。
强连通分量是图中的一个重要概念,它可以帮助我们理解图结构的特性。
3. Tarjan算法步骤Tarjan算法通过深度优先搜索来寻找强连通分量,其具体步骤如下:(1)初始化。
将所有顶点标记为未访问状态,定义一个栈来保存已经访问的顶点。
(2)深度优先搜索。
从图中的任意一个未访问的顶点开始进行深度优先搜索。
(3)标记顶点。
在搜索过程中,对每个顶点进行标记,记录其访问顺序(也称为时间戳)和能够到达的最小时间戳。
(4)寻找强连通分量。
当一个顶点的访问顺序等于能够到达的最小时间戳时,说明它是一个强连通分量的根节点。
通过弹出栈中的顶点,可以找到该强连通分量中的所有顶点。
三、Tarjan算法应用Tarjan算法在图论和算法设计中有着广泛的应用,下面介绍几个常见的应用场景:1. 强连通分量的查找Tarjan算法可以高效地找到图中的所有强连通分量。
这对于解决一些实际问题非常有用,比如社交网络中的群组划分、电路中的等价关系判断等。
2. 有向图的可达性分析在有向图中,Tarjan算法可以用来判断两个顶点之间是否存在路径。
这对于解决一些路径相关的问题非常有帮助,比如寻找关键路径、判断死锁等。
3. 编译器优化Tarjan算法可以用于编译器的优化过程中,通过判断变量的依赖关系来进行代码重排和性能优化。
有向图的强连通分量及应用
图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 】 ;
( 啦 一个有商翻
∞ 将 肚糟璜弹出
强连通分量的定义
强连通分量的定义
强连通分量是图论中的一个概念,指的是在有向图中,若任意两个顶点都存在一条有向路径,则这个有向图就是强连通的。
而强连通分量则指的是有向图中的极大强连通子图,即在该子图中任意两个顶点都是强连通的,并且该子图不能再加入其他的顶点或边使其仍然保持强连通。
在实际应用中,强连通分量有着广泛的应用。
比如在电路设计中,可以将电路看作一个有向图,每个元件看作一个顶点,元件之间的电线则看作一条有向边。
那么在这个电路中,如果存在一个强连通分量,则说明这些元件可以构成一个独立的电路模块,可以方便地进行测试和维护。
此外,在社交网络分析、路网规划等领域,强连通分量也有着重要的应用。
在实际应用中,我们可以通过深度优先搜索(DFS)或者Tarjan算法来求解一个有向图的强连通分量。
具体来说,DFS 算法可以通过遍历有向图来寻找所有的强连通分量;而Tarjan 算法则是一种更高效的算法,可以在O(V+E)的时间复杂度内求解一个有向图的所有强连通分量。
总之,强连通分量是图论中一个重要的概念,在实际应用中有着广泛的应用。
通过深入学习和理解这个概念,我们可以更好地应用它来解决实际问题。
天才少女中提到的特拉亨伯格算法
特拉亨伯格算法特拉亨伯格算法(Tarjan’s algorithm)是一种用于查找图中强连通分量的算法,由美国计算机科学家罗伯特·特拉亨伯格(Robert Tarjan)于1972年提出。
该算法通过深度优先搜索(DFS)和堆栈数据结构来实现,可以高效地识别出图中的所有强连通分量。
强连通分量在讲解特拉亨伯格算法之前,我们先来了解一下什么是强连通分量。
在有向图中,如果从顶点u到v存在一条路径,并且从v到u也存在一条路径,则称顶点u和v是强连通的。
一个强连通分量是指具有相同性质的顶点集合,即其中的任意两个顶点都是强连通的。
强连通分量在很多应用中都有重要作用,例如在社交网络中可以用来发现朋友圈、在编译器优化中可以用来进行代码优化等。
因此,研究如何高效地寻找图中的强连通分量是非常有意义的。
特拉亨伯格算法原理特拉亨伯格算法基于深度优先搜索(DFS)和堆栈数据结构来实现。
它的基本思想是通过DFS遍历图中的每个顶点,并将DFS过程中访问到的顶点按照访问顺序依次压入堆栈。
当DFS遍历完成后,通过遍历堆栈中的顶点,可以找到每个强连通分量。
具体实现步骤如下:1.初始化一个空堆栈和一个空访问数组。
2.对于图中的每个顶点v,如果v没有被访问过,则调用DFS(v)进行深度优先搜索。
3.在DFS(v)函数中,首先将v标记为已访问,并将其压入堆栈。
4.遍历v的所有邻接顶点w,如果w没有被访问过,则递归调用DFS(w)。
5.在递归回溯时,如果发现当前顶点v是一个强连通分量的根节点,则从堆栈中不断弹出元素,直到弹出v为止,并将这些弹出的元素构成一个强连通分量。
6.重复步骤2-5,直到图中所有顶点都被访问过。
算法示例假设我们有以下有向图:A -> BB -> CC -> AB -> DD -> EE -> FF -> DE -> GG -> F我们可以使用特拉亨伯格算法来找出图中的强连通分量。
图论_连通_连通分量
图论_连通_连通分量 强连通图 : 强连通分量就是本⾝ 有向图 ---> ⾮强连通图 : 多个强连通分量图---> 连通图 : 连通分量就是本⾝ ⽆向图 ---> ⾮连通图 : 多个连通分量路径 : 顾名思义.路径长度 : 路径上边的数量.路径 : 顾名思义.路径长度 : 路径上边的数量.连通 : ⽆向图顶点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。
图的连通性检测方法
图的连通性检测方法图论是数学的一个分支,研究图形结构以及图形之间的关系。
在图论中,连通性是一个重要的概念,用于描述图中的节点或顶点之间是否存在路径相连。
连通性检测方法是用来确定一个图是否是连通图的方法。
本文将介绍几种常用的图的连通性检测方法。
一、深度优先搜索(DFS)深度优先搜索是一种常用的图遍历算法,也可以用来检测图的连通性。
该方法从图中的一个顶点开始,沿着一条路径尽可能深的搜索,直到到达无法继续搜索的节点,然后回溯到上一个节点,继续搜索其他路径。
具体步骤如下:1. 选择一个起始节点作为根节点。
2. 遍历该节点的邻接节点,并标记为已访问。
3. 递归的访问未访问过的邻接节点,直到所有节点都被访问过。
4. 如果所有节点都被访问过,则图是连通的;否则,图是不连通的。
DFS算法的时间复杂度为O(V+E),其中V是节点数,E是边数。
二、广度优先搜索(BFS)广度优先搜索也是一种常用的图遍历算法,同样可以用来检测图的连通性。
该方法从图中的一个顶点开始,先访问其所有邻接节点,然后再依次访问它们的邻接节点。
具体步骤如下:1. 选择一个起始节点作为根节点。
2. 将该节点加入一个队列中。
3. 从队列中取出一个节点,并标记为已访问。
4. 遍历该节点的邻接节点,将未访问过的节点加入队列中。
5. 重复步骤3和步骤4,直到队列为空。
6. 如果所有节点都被访问过,则图是连通的;否则,图是不连通的。
BFS算法的时间复杂度同样为O(V+E)。
三、并查集并查集是一种数据结构,常用于解决图的连通性问题。
它可以高效地合并集合和判断元素是否属于同一个集合。
具体步骤如下:1. 初始化并查集,每个节点都是一个独立的集合。
2. 遍历图中的每条边,将边的两个节点合并到同一个集合中。
3. 判断图是否连通的方法是查找两个节点是否属于同一个集合。
并查集的时间复杂度为O(V+E)。
四、最小生成树最小生成树是指一个连通图的生成树,其所有边的权值之和最小。
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算法对逻辑电路进行布线和优化。
Tarjan算法过程动态演示
√
v=2
v=4
v=4
4√
dfn[4]=6 u=4 low[4]=65
X
栈S:
64
35
2
dfn[1]=2
2 u=2 low[1]=12
v=5
v=6
v=3
dfn[1]=3
5√
u=5
dfn[5]=5 low[5]=15
X
6
u=3 low[1]=3
√
v=6
dfn[6]=4 u=6 low[6]=4
3
√
6 √ dfn[1]=4
三、Taห้องสมุดไป่ตู้jan算法求强连通分量的程序实现
4、假设我们已经dfs完了u的所有的子树,那么之后无论我们再怎么dfs, u点的low值已经不会再变了
那么如果dfn[u]=low[u]这说明了什么呢? 再结合一下dfn和low的定义来看 看吧 dfn表示u点被dfs到的时间,low表示u和u所有的子树所能到达的点中dfn 最小的。 这说明了u点及u点之下的所有子节点没有边是指向u的祖先的了,即 我们之前说的u点与它的子孙节点构成了一个最大的强连通图即强连通分量 此 时我们得到了一个强连通分量,把所有的u点以后压入栈中的点和u点一并弹出, 将它们的vis[ ]置为false,如有需要也可以给它们染上相同颜色(后面会用到) 于是tarjan求强连通分量的部分到此结束
有了以上定义之后,tarjan算法就有了一条关键的定理: 定理: d[i]=low[i] <=> 节点 i 是一个scc root 简单理解就是,同一个SCC中的节点只有scc root 满足d[i]=low[i],为什么呢?
二、如何求强连通分量——Tarjan算法
刚才说过了,Tarjan算法求解SCC已经转换为求解scc root的问题,而这条 定理给出了scc root的求解方法,至此,整个流程就通了:dfs遍历原图,递归 计算low[i],节点i递归遍历完成后,如果发现d[i]=low[i]则,找到了一个SCC,当 然,这其中还涉及到找到scc root之后,如何根据scc root 得到一个scc的问题, 其实tarjan算法dfs过程中,用栈来记录访问到的节点,找到scc root之后,从栈 顶一次弹出节点,直到遇到scc root节点,便可构成一个scc,下面的算法实现 描述了这一过程。
强连通分量的三种算法
有向图中, 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来说)就可以了。
每次深搜都得到一个强连通分量。
隐藏性质:分析到这里,我们已经知道怎么求强连通分量了。
但是,大家有没有注意到我们在第二次深搜选择树的顺序有一个特点呢?如果在看上述思路的时候,你的脑子在思考,相信你已经知道了!!!它就是:如果我们把求出来的每个强连通分量收缩成一个点,并且用求出每个强连通分量的顺序来标记收缩后的节点,那么这个顺序其实就是强连通分量收缩成点后形成的有向无环图的拓扑序列。
为什么呢?首先,应该明确搜索后的图一定是有向无环图呢?废话,如果还有环,那么环上的顶点对应的所有原来图上的顶点构成一个强连通分量,而不是构成环上那么多点对应的独自的强连通分量了。
求有向图的强连通分量个数(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。
强连通算法--Tarjan个人理解+详解
强连通算法--Tarjan个⼈理解+详解⾸先我们引⼊定义:1、有向图G中,以顶点v为起点的弧的数⽬称为v的出度,记做deg+(v);以顶点v为终点的弧的数⽬称为v的⼊度,记做deg-(v)。
2、如果在有向图G中,有⼀条<u,v>有向道路,则v称为u可达的,或者说,从u可达v。
3、如果有向图G的任意两个顶点都互相可达,则称图 G是强连通图,如果有向图G存在两顶点u和v使得u不能到v,或者v不能到u,则称图G 是强⾮连通图。
4、如果有向图G不是强连通图,他的⼦图G2是强连通图,点v属于G2,任意包含v的强连通⼦图也是G2的⼦图,则乘G2是有向图G的极⼤强连通⼦图,也称强连通分量。
5、什么是强连通?强连通其实就是指图中有两点u,v。
使得能够找到有向路径从u到v并且也能够找到有向路径从v到u,则称u,v是强连通的。
然后我们理解定义:既然我们现在已经了解了什么是强连通,和什么是强连通分量,可能⼤家对于定义还是理解的不透彻,我们不妨引⼊⼀个图加强⼤家对强连通分量和强连通的理解:标注棕⾊线条框框的三个部分就分别是⼀个强连通分量,也就是说,这个图中的强连通分量有3个。
其中我们分析最左边三个点的这部分:其中1能够到达0,0也能够通过经过2的路径到达1.1和0就是强连通的。
其中1能够通过0到达2,2也能够到达1,那么1和2就是强连通的。
.........同理,我们能够看得出来这⼀部分确实是强连通分量,也就是说,强连通分量⾥边的任意两个点,都是互相可达的。
那么如何求强连通分量的个数呢?另外强连通算法能够实现什么⼀些基本操作呢?我们继续详解、接着我们开始接触算法,讨论如何⽤Tarjan算法求强连通分量个数:Tarjan算法,是⼀个基于Dfs的算法(如果⼤家还不知道什么是Dfs,⾃⾏百度学习),假设我们要先从0号节点开始Dfs,我们发现⼀次Dfs 我萌就能遍历整个图(树),⽽且我们发现,在Dfs的过程中,我们深搜到了其他强连通分量中,那么俺们Dfs之后如何判断他喵的哪个和那些节点属于⼀个强连通分量呢?我们⾸先引⼊两个数组:①dfn【】②low【】第⼀个数组dfn我们⽤来标记当前节点在深搜过程中是第⼏个遍历到的点。
Kosaraju算法详解
Kosaraju算法详解Kosaraju算法可以求出有向图中的强连通分量个数,并且对分属于不同强连通分量的点进⾏标记。
它的算法描述较为简单:(1) 第⼀次对图G进⾏DFS遍历,并在遍历过程中,记录每⼀个点的退出顺序。
以下图为例:如果以1为起点遍历,访问结点的顺序如下:结点第⼆次被访问即为退出之时,那么我们可以得到结点的退出顺序:(2)倒转每⼀条边的⽅向,构造出⼀个反图G’。
然后按照退出顺序的逆序对反图进⾏第⼆次DFS遍历。
我们按1、4、2、3、5的逆序第⼆次DFS遍历:访问过程如下:每次遍历得到的那些点即属于同⼀个强连通分量。
1、4属于同⼀个强连通分量,2、3、5属于另⼀个强连通分量。
代码:1 #include<iostream>2 #include<cstdio>3 #include<cstring>4using namespace std;5int map[101][101];6int nmap[101][101];7int pass[101];8int vis[101];9int now=1;10int n,m;11int num=0;12void dfs(int p)13 {14 vis[p]=1;15for(int i=1;i<=n;i++)16 {17if(vis[i]==0&&map[p][i]==1)18 {19 dfs(i);2021 }22 }23 pass[now]=p;24 now++;25 }26void ndfs(int p)27 {28 vis[p]=0;29for(int i=1;i<=n;i++)30 {31if(vis[i]==1&&nmap[p][i]==1)32 ndfs(i);33 }34 }35int main()36 {3738 scanf("%d%d",&n,&m);39for(int i=1;i<=m;i++)40 {41int x,y;42 scanf("%d%d",&x,&y);43 map[x][y]=1;44 nmap[y][x]=1;45 }46for(int i=1;i<=n;i++)47 {48if(vis[i]==0)49 dfs(i);50 }51 pass[now]=1;52for(int i=n;i>=1;i--)53 {54if(vis[pass[i]]==1)55 {56 ndfs(pass[i]);57 num++;58 }59 }60 cout<<num;61return0;62 }。
Tarjan算法
Tarjan算法Tarjan算法Tarjan求强连通分量概念:如果两个顶点互相可达,则它们是强连通的。
如果⼀幅有向图中任意两个顶点都是强连通的,则这幅有向图也是强连通的。
强连通分量就是图中具有连通性的⼀个最⼤⼦集,⼀般可以⽤来缩点,即相互到达的⼀堆点可以将他们有⽤的信息统⼀到⼀个点上去。
求解强连通分量的⽅法⼀般会使⽤Tarjan算法。
⾸先我们需要学会dfs树,定义⼏种边:树边,连接dfs树中的⽗节点和⼦节点的边。
横叉边,连接dfs树中的不在⼀个⼦树中的两个节点的边。
前向边,连接dfs树中的⼀个节点连向他的⼦树中深度差⼤于等于2,也就是不直接相连的两点的边。
后向边,连接dfs树中的⼀个节点连向他的祖先的边,⼜叫返祖边。
求解:dfn[i]表⽰i节点的时间戳,low[i]表⽰i节点的所属的强连通分量i的时间戳的最⼩值,也可以说是i或i的⼦树能够追溯到的最早的栈中节点的时间戳。
发现如果在dfs树中出现了后向边说明后向边连接的两点之间的环⼀定是强连通的,但不⼀定是最⼤⼦集。
我们有个性质:如果low[i]⽐dfn[i]⼩,说明他有⼀条连向祖宗的后向边,所以要保留在栈中。
如果当前low[i]等于dfn[i]说明他搜索的栈中没有可以连向他祖宗的边,当前栈中的点⼀定就是low[栈中的点]等于dfn[i]的点,也可以有能连向i的反向边。
有如果树边没有被找到过,则有low[u]=min(low[u],low[v]);也就是⽤v的所能连向的点的最⼩时间戳来更新u。
如果此点已经⼊栈,说明此时这是⼀条回边,因此不能⽤low[v]更新,⽽应该:low[u]=min(low[u],dfn[v]);因为,low[v]可能属于另⼀个编号更⼩的强连通分量⾥,⽽u可以连接到v但v不⼀定与u相连,所以可能u、v并不属于同⼀个强连通分量。
但是第⼀种情况如果low[v]⽐low[u]⼩的话,v⼀定有包括u的强连通分量,Tarjan求割点和桥概念:在⽆向连通图中,双连通分量分为点双和边双,点双为⼀个连通分量删去任意⼀个点都保持连通,边双为删去任意⼀条边都保持连通。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
求强连通分量的几种算法的实现与分析作者:陈燕,江克勤来源:《电脑知识与技术》2011年第09期摘要:有向图的强连通性是图论中的经典问题,有着很多重要的应用。
该文给出了求强连通分量的Kosaraju、Tarjan和Gabow三个算法的具体实现,并对算法的效率进行了分析。
关键词:强连通分量;深度优先搜索;Kosaraju算法;Tarjan算法;Gabow算法中图分类号:TP312文献标识码:A文章编号:1009-3044(2011)09-2140-03The Implementation and Analysis of Several Algorithms About Strongly Connected Components CHEN Yan1, JIANG Ke-qin2(1.Nanjing Health Inspection Bureau, Nanjing 210003, China; 2.School of Computer and Information, Anqing Teachers College, Anqing 246011, China)Abstract: Digraph of strong connectivity is the classic problems in graph theory, which arises in many important applications. In this paper, the detailed implementation of Kosaraju, Tarjan and Gabow algorithms is discussed for solving strongly connected components, and the efficiency of three algorithms is analyzed.Key words: strongly connected components; depth first search; Kosaraju; Tarjan; Gabow图的连通性是图论中的经典问题,所谓连通性,直观地讲,就是“连成一片”。
无向图的连通分量的计算比较容易实现,可以通过对该无向图进行深度优先搜索或广度优先搜索,得到其中的各个连通分量的顶点集。
但对有向图的强连通分量的计算,则比较麻烦,“离散数学”、“数据结构”等课程中也没有详述。
在一些ACM竞赛题中,往往最后是将问题转化为求有向图的强连通分量,另外在2-SAT问题[1]的求解中也用到强连通分量的计算。
强连通分量的计算通常有三种算法:Kosaraju算法、Tarjan算法和Gabow算法。
下面分别给出这三种算法的原理及其实现。
1 有向图的强连通分量及其计算1.1 有向图的强连通性设G为有向图,若G中存在一条以 u为起点 v为终点的有向路P,则称从 u到 v是可达的。
如果G的任何两个顶点都是相互可达的,则称图G是强连通的[2]。
所谓强连通分量(Strongly Connected Components),指的是图中的极大强连通子图。
如果有向图G中只有唯一一个强连通分量,那么G是强连通图。
若有向图G的强连通分量数大于1,则G为非强连通图。
图1的有向图G是非强连通图,它有三个强连通分量,如图2所示。
1.2 Kosaraju算法Kosaraju算法[3]是基于对有向图及其逆图进行两次深度优先搜索的算法。
首先从某个顶点出发沿以该顶点为尾的弧进行深度优先搜索遍历,并按其所有邻接点的搜索都完成(即退出DFS函数)的顺序将顶点排列起来,为此在退出DFS函数之前将完成搜索的顶点号记录在辅助数组finished中;接着从最后完成搜索的顶点(即最后存入finished数组的顶点)出发,沿着以该顶点为头的弧作逆向深度优先搜索遍历,若此次遍历不能访问到有向图中所有顶点,则从余下的顶点中最后完成搜索的那个顶点出发,继续作逆向深度优先搜索遍历,直至有向图中所有顶点都被访问到为止。
这样,每一次作逆向深度优先搜索遍历所访问到的顶点集就是有向图的一个强连通分量的顶点集。
为了方便进行两次正向和逆向的深度优先搜索,这里采用十字链表作为有向图的存储结构,建立有向图的算法参见“数据结构”教材[4]的算法7.3。
Kosaraju算法的实现如下(分别调用DFSTraverse1_1(G)和DFSTraverse1_2(G)):void DFS1_1(OLGraph G,int i){visited[i]=TRUE; // 访问标志数组置1(已被访问)p=G.xlist[i].firstout; // p指向以i为弧尾的第一个顶点while(p){if(!visited[p->headvex]) // 该弧的头顶点未被访问DFS1_1(G,p->headvex);p=p->tlink; }finished[count++]=i;}void DFSTraverse1_1(OLGraph G) // 产生数组finished[]的值{ for(v=0;vvisited[v]=FALSE; // 访问标志数组置初值0(未被访问)for(v=0;vif(!visited[v]) DFS1_1(G,v);}void DFS1_2(OLGraph G,int i){visited[i]=TRUE; // 访问标志数组置1(已被访问)coutp=G.xlist[i].firstin; // p指向以i为弧头的第一个顶点while(p){if(!visited[p->tailvex]) // 该弧的尾顶点未被访问DFS1_2(G,p->tailvex);p=p->hlink; }}void DFSTraverse1_2(OLGraph G){ // 从finished[G.vexnum-1]中的顶点开始,逆向深度优先遍历有向图G int v,n=0;for(v=0;vvisited[v]=FALSE; // 访问标志数组置初值0(未被访问)for(v=G.vexnum-1;v>=0;v--) // 由finished[G.vexnum-1]至finished[0],查找 if(!visited[finished[v]]){ coutDFS1_2(G,finished[v]); cout}1.3 Tarjan算法Tarjan算法[5]也是基于对有向图进行深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。
搜索时,把当前搜索树中未处理的顶点入栈Stack,回溯时再判断栈顶到栈中的顶点是否构成一个强连通分量。
为此定义两个数组DFN和LOW,DFN[u]记录顶点u搜索的次序编号(时间戳),LOW[u]记录u或u的子树能够追溯到的最早的栈中顶点的次序号。
Tarjan算法的实现如下(调用DFSTraverse2(G)):int instack[MAX_VERTEX_NUM]; // 判断是否在栈Stack中的标志数组int index=0,top=0,n=0;void DFS2(OLGraph G,int i){ visited[i]=TRUE; // 访问标志数组置1(已被访问)DFN[i]=LOW[i]=++index;Stack[++top]=i; instack[i]=TRUE; // i在栈中p=G.xlist[i].firstout; // p指向以i为弧尾的第一个顶点while(p){if(!visited[p->headvex]) // 该弧的头顶点未被访问{ DFS2(G,p->headvex);if(LOW[p->headvex]LOW[i]=LOW[p->headvex];}else if(instack[p->headvex]&&DFN[p->headvex]LOW[i]=DFN[p->headvex]; // 若顶点p->headvex还在栈中,修改LOWp=p->tlink; }if(DFN[i]==LOW[i]) // 此时顶点i是强连通分量的根{coutdo { j=Stack[top--]; // 退栈的j为该强连通分量中的顶点instack[j]=FALSE; // j不在栈中cout}while(j!=i);cout}void DFSTraverse2(OLGraph G) // Tarjan算法{for(v=0;vvisited[v]=FALSE; // 访问标志数组置初值0(未被访问)for(v=0;vif(!visited[v]) DFS2(G,v);}1.4 Gabow算法Gabow算法是对Tarjan算法的改进,通过两个栈来维持一个不断增加的路径,其中一个栈用来存储路径上的顶点,另外一个栈用来存储边界,即不用LOW数组,增加辅助栈Stack2存储边界,数组DFN和instack及栈Stack的定义同Tarjan算法。
Gabow算法的实现如下(调用DFSTraverse3(G)):void DFS3(OLGraph G,int i){ visited[i]=TRUE; // 访问标志数组置1(已被访问)DFN[i]=++index; Stack2[++top2]=i;Stack[++top]=i; instack[i]=TRUE; // i在栈Stack中p=G.xlist[i].firstout; // p指向以i为弧尾的第一个顶点while(p){if(!visited[p->headvex]) // 该弧的头顶点未被访问DFS3(G,p->headvex);else if(instack[p->headvex])// 若该点访问过,但还在栈Stack中未删除,则修改栈Stack2 { while(DFN[Stack2[top2]] >DFN[p->headvex]) top2--; }// 删除Stack2中构成环的顶点p=p->tlink; }if(i==Stack2[top2]) // 此时顶点i是强连通分量的根{top2--; coutdo { j=Stack[top--]; // 退栈的j为该强连通分量中的顶点instack[j]=FALSE; // j不在栈Stack中cout}while(j!=i);cout}void DFSTraverse3(OLGraph G) // Gabow算法{for(v=0;vvisited[v]=FALSE; // 访问标志数组置初值0(未被访问)for(v=0;vif(!visited[v]) DFS3(G,v);}2 结论为了统一,这里三个算法的实现都采用了十字链表作为有向图的存储表示,其实后两个算法用邻接表存储有向图即可。