通过深度优先搜索求强连通分量
dfss案例
dfss案例DFS(深度优先搜索)是一种用于图遍历或搜索的算法,它以递归的方式遍历或搜索图中的节点。
在本文中,我们将以DFS案例为题,列举一些常见的应用场景和实例,来说明DFS算法的作用和用途。
1. 连通性检测:DFS可以用来检测图中的连通分量。
通过从一个起始节点开始,递归地访问所有相邻节点,可以判断图是否连通,以及得到图中的连通分量。
2. 深度优先生成树:DFS可以生成一棵深度优先生成树,该树用于表示图中的节点之间的关系。
通过递归地遍历图中的节点,可以建立起一棵树,其中每个节点的子节点都是其相邻节点。
3. 拓扑排序:DFS可以用于拓扑排序,即对有向无环图(DAG)中的节点进行排序。
通过从任意一个节点开始进行DFS遍历,并在递归返回时记录节点的顺序,可以得到一个拓扑排序序列。
4. 寻找图中的环:DFS可以用于寻找图中的环。
通过递归地遍历图中的节点,并记录访问过的节点,可以检测到是否存在环。
如果在遍历过程中遇到已经访问过的节点,则说明存在环。
5. 最短路径问题:DFS可以用于解决最短路径问题。
通过递归地遍历图中的节点,并记录路径长度,可以找到从起始节点到目标节点的最短路径。
6. 迷宫求解:DFS可以用于解决迷宫求解问题。
将迷宫表示为图的形式,通过递归地遍历图中的节点,可以找到从起点到终点的路径。
7. 数独求解:DFS可以用于解决数独问题。
通过递归地遍历数独中的格子,并尝试填入数字,可以找到数独的解。
8. 二叉树的遍历:DFS可以用于二叉树的遍历。
通过递归地遍历二叉树的左子树和右子树,可以得到前序遍历、中序遍历和后序遍历的结果。
9. 图的着色问题:DFS可以用于解决图的着色问题。
通过递归地遍历图中的节点,并给节点标记颜色,可以实现对图的着色。
10. 剪枝问题:DFS可以用于解决剪枝问题。
通过递归地遍历搜索树,并在搜索过程中进行剪枝操作,可以减少不必要的搜索。
以上是DFS算法的一些常见应用场景和实例。
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 的强联通分量。
tarjan算法简洁模板 -回复
tarjan算法简洁模板-回复什么是Tarjan算法?Tarjan算法是一种基于深度优先搜索(DFS)的图算法,用于寻找有向图中的强连通分量(Strongly Connected Component, SCC)。
它由美国计算机科学家Robert Tarjan在1972年提出,并被广泛应用于图论和网络分析等领域。
为什么需要寻找强连通分量?强连通分量是一种图中具有特殊性质的子图,其中的任意两个顶点都可以通过有向边相互到达。
在网络分析和图论中,强连通分量代表了一组高度相互关联的节点,具有重要的意义。
例如,在社交网络中,强连通分量可能表示具有密切联系的朋友圈;在软件工程中,强连通分量可以帮助识别出相互依赖的模块。
Tarjan算法的思想是什么?Tarjan算法的基本思想是通过DFS遍历图,对每个节点进行编号和标记,并根据节点的低点值(Low Point)进行划分,以构建强连通分量。
具体步骤如下:1. 初始化变量:创建一个空栈,用于存储访问过的节点;初始化节点编号为0,用一个数组记录每个节点的索引号;初始化节点的低点值为节点编号,用一个数组记录每个节点的低点值;初始化一个布尔型数组,用于标记节点是否在栈中。
2. 对每个未访问的节点u,调用DFS函数进行递归遍历。
3. 在DFS函数中,首先给节点u赋予一个唯一的索引号,并将节点u入栈,并将节点u的索引号和低点值设置为当前节点计数器。
然后遍历u的所有邻接节点v,如果v未被访问,则递归调用DFS函数。
4. 在递归调用的过程中,更新节点u的低点值为u和v的最小值,并将已经访问过的节点v标记为在栈中。
5. 当递归调用结束后,如果节点u的索引号等于低点值,说明节点u开始构成了一个强连通分量。
从栈顶依次弹出节点,直到节点u。
被弹出的节点就是强连通分量的一部分,将这些节点存储起来。
6. 重复以上步骤,直到所有节点都被访问过。
如何实现Tarjan算法?以下是一种简洁的Python实现Tarjan算法的模板:pythondef tarjan(graph):index = {} # 用于记录每个节点的索引号low = {} # 用于记录每个节点的低点值stack = [] # 用于存储访问过的节点visited = {} # 用于标记节点是否在栈中result = [] # 存储所有的强连通分量# 初始化变量def dfs(node):index[node] = len(index)low[node] = len(low)stack.append(node)visited[node] = Truefor neighbor in graph[node]:if neighbor not in index:dfs(neighbor)low[node] = min(low[node], low[neighbor]) elif neighbor in visited:low[node] = min(low[node], index[neighbor])if index[node] == low[node]:component = []while stack[-1] != node:component.append(stack.pop())visited.pop(component[-1])component.append(stack.pop())visited.pop(component[-1])result.append(component)for node in graph:if node not in index:dfs(node)return result总结:Tarjan算法是一种基于深度优先搜索的图算法,用于查找有向图中的强连通分量。
强连通分量个数的最小值
强连通分量个数的最小值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算法可以用于编译器的优化过程中,通过判断变量的依赖关系来进行代码重排和性能优化。
强连通分量的定义
强连通分量的定义
强连通分量是图论中的一个概念,指的是在有向图中,若任意两个顶点都存在一条有向路径,则这个有向图就是强连通的。
而强连通分量则指的是有向图中的极大强连通子图,即在该子图中任意两个顶点都是强连通的,并且该子图不能再加入其他的顶点或边使其仍然保持强连通。
在实际应用中,强连通分量有着广泛的应用。
比如在电路设计中,可以将电路看作一个有向图,每个元件看作一个顶点,元件之间的电线则看作一条有向边。
那么在这个电路中,如果存在一个强连通分量,则说明这些元件可以构成一个独立的电路模块,可以方便地进行测试和维护。
此外,在社交网络分析、路网规划等领域,强连通分量也有着重要的应用。
在实际应用中,我们可以通过深度优先搜索(DFS)或者Tarjan算法来求解一个有向图的强连通分量。
具体来说,DFS 算法可以通过遍历有向图来寻找所有的强连通分量;而Tarjan 算法则是一种更高效的算法,可以在O(V+E)的时间复杂度内求解一个有向图的所有强连通分量。
总之,强连通分量是图论中一个重要的概念,在实际应用中有着广泛的应用。
通过深入学习和理解这个概念,我们可以更好地应用它来解决实际问题。
深度优先搜索算法
深度优先搜索算法深度优先搜索算法(Depth-First Search,DFS)是一种用于遍历或搜索树或图数据结构的算法。
在DFS中,我们会尽可能深地探索一个分支,直到无法继续为止,然后回溯到前一个节点,继续探索其他分支。
DFS通常使用递归或栈数据结构来实现。
在本文中,我们将深入探讨DFS的原理、实现方法、应用场景以及一些相关的扩展主题。
1.原理深度优先搜索算法的原理非常简单。
从图或树的一个起始节点开始,我们首先探索它的一个邻居节点,然后再探索这个邻居节点的一个邻居节点,依此类推。
每次都尽可能深地探索一个分支,直到无法继续为止,然后回溯到前一个节点,继续探索其他分支。
这个过程可以用递归或栈来实现。
2.实现方法在实现DFS时,我们可以使用递归或栈来维护待访问的节点。
下面分别介绍这两种实现方法。
2.1递归实现递归是实现DFS最直观的方法。
我们可以定义一个递归函数来表示探索节点的过程。
该函数接受当前节点作为参数,并在该节点上进行一些操作,然后递归地调用自身来探索当前节点的邻居节点。
这样就可以很容易地实现DFS。
```pythondef dfs(node, visited):visited.add(node)#对当前节点进行一些操作for neighbor in node.neighbors:if neighbor not in visited:dfs(neighbor, visited)```2.2栈实现除了递归,我们还可以使用栈来实现DFS。
我们首先将起始节点入栈,然后循环执行以下步骤:出栈一个节点,对该节点进行一些操作,将其未访问的邻居节点入栈。
这样就可以模拟递归的过程,实现DFS。
```pythondef dfs(start):stack = [start]visited = set()while stack:node = stack.pop()if node not in visited:visited.add(node)#对当前节点进行一些操作for neighbor in node.neighbors:if neighbor not in visited:stack.append(neighbor)```3.应用场景深度优先搜索算法在实际的软件开发中有着广泛的应用。
深度优先搜索算法利用深度优先搜索解决迷宫问题
深度优先搜索算法利用深度优先搜索解决迷宫问题深度优先搜索算法(Depth-First Search, DFS)是一种常用的图遍历算法,它通过优先遍历图中的深层节点来搜索目标节点。
在解决迷宫问题时,深度优先搜索算法可以帮助我们找到从起点到终点的路径。
一、深度优先搜索算法的实现原理深度优先搜索算法的实现原理相当简单直观。
它遵循以下步骤:1. 选择一个起始节点,并标记为已访问。
2. 递归地访问其相邻节点,若相邻节点未被访问,则标记为已访问,并继续访问其相邻节点。
3. 重复步骤2直到无法继续递归访问,则返回上一级节点,查找其他未被访问的相邻节点。
4. 重复步骤2和3,直到找到目标节点或者已经遍历所有节点。
二、利用深度优先搜索算法解决迷宫问题迷宫问题是一个经典的寻找路径问题,在一个二维的迷宫中,我们需要找到从起点到终点的路径。
利用深度优先搜索算法可以很好地解决这个问题。
以下是一种可能的解决方案:```1. 定义一个二维数组作为迷宫地图,其中0代表通路,1代表墙壁。
2. 定义一个和迷宫地图大小相同的二维数组visited,用于记录节点是否已经被访问过。
3. 定义一个存储路径的栈path,用于记录从起点到终点的路径。
4. 定义一个递归函数dfs,参数为当前节点的坐标(x, y)。
5. 在dfs函数中,首先判断当前节点是否为终点,如果是则返回True,表示找到了一条路径。
6. 然后判断当前节点是否越界或者已经访问过,如果是则返回False,表示该路径不可行。
7. 否则,将当前节点标记为已访问,并将其坐标添加到path路径中。
8. 依次递归访问当前节点的上、下、左、右四个相邻节点,如果其中任意一个节点返回True,则返回True。
9. 如果所有相邻节点都返回False,则将当前节点从path路径中删除,并返回False。
10. 最后,在主函数中调用dfs函数,并判断是否找到了一条路径。
```三、示例代码```pythondef dfs(x, y):if maze[x][y] == 1 or visited[x][y] == 1:return Falseif (x, y) == (end_x, end_y):return Truevisited[x][y] = 1path.append((x, y))if dfs(x+1, y) or dfs(x-1, y) or dfs(x, y+1) or dfs(x, y-1): return Truepath.pop()return Falseif __name__ == '__main__':maze = [[0, 1, 1, 0, 0],[0, 0, 0, 1, 0],[1, 1, 0, 0, 0],[1, 1, 1, 1, 0],[0, 0, 0, 1, 0]]visited = [[0] * 5 for _ in range(5)]path = []start_x, start_y = 0, 0end_x, end_y = 4, 4if dfs(start_x, start_y):print("Found path:")for x, y in path:print(f"({x}, {y}) ", end="")print(f"\nStart: ({start_x}, {start_y}), End: ({end_x}, {end_y})") else:print("No path found.")```四、总结深度优先搜索算法是一种有效解决迷宫问题的算法。
天才少女中提到的特拉亨伯格算法
特拉亨伯格算法特拉亨伯格算法(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我们可以使用特拉亨伯格算法来找出图中的强连通分量。
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 开始的连通分量中的所有顶点出栈,并输出为一个强连通分量。
图的连通性检测方法
图的连通性检测方法图论是数学的一个分支,研究图形结构以及图形之间的关系。
在图论中,连通性是一个重要的概念,用于描述图中的节点或顶点之间是否存在路径相连。
连通性检测方法是用来确定一个图是否是连通图的方法。
本文将介绍几种常用的图的连通性检测方法。
一、深度优先搜索(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)。
四、最小生成树最小生成树是指一个连通图的生成树,其所有边的权值之和最小。
算法导论第十五章习题答案
算法导论第十五章习题答案算法导论第十五章习题答案算法导论是一本经典的计算机科学教材,其中第十五章涵盖了图算法的内容。
本文将针对该章节中的习题进行解答,并对其中一些问题进行深入探讨。
1. 习题15.1-1题目要求证明:对于任意的有向图G=(V,E),如果图中不存在从节点u到节点v的路径,则在每个强连通分量中,节点u和节点v都在同一个强连通分量中。
证明:假设存在一个强连通分量C,其中节点u在C中,节点v在C'中(C'为除了C之外的其他强连通分量)。
由于不存在从u到v的路径,所以在C中不存在从u到v的路径。
但是根据强连通分量的定义,C中的任意两个节点之间都存在路径。
所以存在一条从v到u的路径。
这与C'中的节点v不在C中矛盾,所以假设不成立,节点u和节点v必定在同一个强连通分量中。
2. 习题15.2-2题目要求证明:在一个有向无环图中,存在一个拓扑排序,使得任意两个非根节点u和v,u在v之前。
证明:假设存在一个有向无环图G=(V,E),不存在上述所要求的拓扑排序。
即对于任意的拓扑排序,存在两个非根节点u和v,u在v之后。
那么我们可以得到一条从u到v的路径。
由于图中不存在环,所以路径上的节点不会重复。
我们可以将路径上的节点按照拓扑排序的顺序排列,得到一个新的拓扑排序,使得u在v之前。
这与假设矛盾,所以原命题成立。
3. 习题15.3-3题目要求证明:在一个有向图G=(V,E)中,如果存在一条从节点u到节点v的路径,那么在图的转置G^T中,存在一条从节点v到节点u的路径。
证明:假设存在一条从节点u到节点v的路径。
那么在图的转置G^T中,边(u,v)变成了边(v,u)。
所以存在一条从节点v到节点u的路径。
因此,原命题成立。
4. 习题15.4-1题目要求:给出一个算法,判断一个有向图G=(V,E)是否是有向无环图。
算法思路:我们可以使用深度优先搜索(DFS)来判断是否存在环。
具体步骤如下:1. 对于图中的每个节点v,设置一个状态标记visited[v]为false。
深度优先搜索算法
深度优先搜索算法深度优先搜索算法是一种经典的算法,它在计算机科学领域中被广泛应用。
深度优先搜索算法通过沿着一个分支尽可能的往下搜索,直到搜索到所有分支的末端后,返回上一层节点,再继续往下搜索其它分支。
在搜索过程中,深度优先搜索算法采用递归的方式进行,它的工作原理与树的先序遍历算法相似。
本文将介绍深度优先搜索算法的基本原理、应用场景、实现方式及其优缺点等内容。
一、深度优先搜索算法的基本原理深度优先搜索算法是一种基于贪心法的搜索算法,它的目标是在搜索过程中尽可能的向下搜索,直到遇到死路或者找到了目标节点。
当搜索到一个节点时,首先将该节点标记为已访问。
然后从它的相邻节点中选择一个未被访问过的节点继续搜索。
如果没有未被访问过的节点,就返回到前一个节点,从该节点的其它相邻节点开始继续搜索。
这样不断地递归下去,直到搜索到目标节点或者搜索完所有的节点。
深度优先搜索算法的实现方式通常是通过递归函数的方式进行。
假设我们要搜索一棵树,从根节点开始进行深度优先搜索。
可以采用以下的伪代码:```function depthFirstSearch(node)://标记节点为已访问node.visited = true//递归搜索该节点的相邻节点for each adjacentNode in node.adjacentNodes:if adjacentNode.visited == false:depthFirstSearch(adjacentNode)```这段代码表示了深度优先搜索算法的基本思想。
在搜索过程中,首先将当前节点标记为已访问,然后递归搜索该节点的相邻节点。
如果相邻节点未被访问过,就以该节点为起点继续深度优先搜索。
通过递归函数不断往下搜索,最终遍历完整棵树。
二、深度优先搜索算法的应用场景深度优先搜索算法在计算机科学领域中有很多应用,例如图论、路径查找、迷宫和游戏等领域。
下面介绍一些具体的应用场景。
1.图论深度优先搜索算法被广泛应用于图论中。
深度优先搜索和广度优先搜索的比较和应用场景
深度优先搜索和广度优先搜索的比较和应用场景在计算机科学中,深度优先搜索(DFS)和广度优先搜索(BFS)是两种常用的图搜索算法。
它们在解决许多问题时都能够发挥重要作用,但在不同的情况下具有不同的优势和适用性。
本文将对深度优先搜索和广度优先搜索进行比较和分析,并讨论它们在不同应用场景中的使用。
一、深度优先搜索(DFS)深度优先搜索是一种通过遍历图的深度节点来查找目标节点的算法。
它的基本思想是从起始节点开始,依次遍历该节点的相邻节点,直到到达目标节点或者无法继续搜索为止。
如果当前节点有未被访问的相邻节点,则选择其中一个作为下一个节点继续进行深度搜索;如果当前节点没有未被访问的相邻节点,则回溯到上一个节点,并选择其未被访问的相邻节点进行搜索。
深度优先搜索的主要优势是其在搜索树的深度方向上进行,能够快速达到目标节点。
它通常使用递归或栈数据结构来实现,代码实现相对简单。
深度优先搜索适用于以下情况:1. 图中的路径问题:深度优先搜索能够在图中找到一条路径是否存在。
2. 拓扑排序问题:深度优先搜索能够对有向无环图进行拓扑排序,找到图中节点的一个线性排序。
3. 连通性问题:深度优先搜索能够判断图中的连通分量数量以及它们的具体节点组合。
二、广度优先搜索(BFS)广度优先搜索是一种通过遍历图的广度节点来查找目标节点的算法。
它的基本思想是从起始节点开始,先遍历起始节点的所有相邻节点,然后再遍历相邻节点的相邻节点,以此类推,直到到达目标节点或者无法继续搜索为止。
广度优先搜索通常使用队列数据结构来实现。
广度优先搜索的主要优势是其在搜索树的广度方向上进行,能够逐层地搜索目标节点所在的路径。
它逐层扩展搜索,直到找到目标节点或者遍历完整个图。
广度优先搜索适用于以下情况:1. 最短路径问题:广度优先搜索能够在无权图中找到起始节点到目标节点的最短路径。
2. 网络分析问题:广度优先搜索能够在图中查找节点的邻居节点、度数或者群组。
三、深度优先搜索和广度优先搜索的比较深度优先搜索和广度优先搜索在以下方面有所不同:1. 搜索顺序:深度优先搜索按照深度优先的顺序进行搜索,而广度优先搜索按照广度优先的顺序进行搜索。
强连通分量的三种算法
有向图中, 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来说)就可以了。
每次深搜都得到一个强连通分量。
隐藏性质:分析到这里,我们已经知道怎么求强连通分量了。
但是,大家有没有注意到我们在第二次深搜选择树的顺序有一个特点呢?如果在看上述思路的时候,你的脑子在思考,相信你已经知道了!!!它就是:如果我们把求出来的每个强连通分量收缩成一个点,并且用求出每个强连通分量的顺序来标记收缩后的节点,那么这个顺序其实就是强连通分量收缩成点后形成的有向无环图的拓扑序列。
为什么呢?首先,应该明确搜索后的图一定是有向无环图呢?废话,如果还有环,那么环上的顶点对应的所有原来图上的顶点构成一个强连通分量,而不是构成环上那么多点对应的独自的强连通分量了。
2023年数学建模国赛c题第三问
2023年数学建模国赛c题第三问随着科技的不断发展,数学建模在解决实际问题中发挥着重要作用。
2023年的数学建模国赛C题第三问将考察参赛选手们在数学建模领域的能力和创新思维。
本文将从问题的分析、模型的建立和求解方法等方面进行探讨。
一、问题的分析在解决问题之前,我们首先要对问题进行全面的分析。
2023年数学建模国赛C题第三问要求我们设计一种新的算法,能够在一个有向图中找出所有的强连通分量。
给定一个有向图,强连通分量指的是其中任意两个顶点之间都存在一条有向路径。
考生需要设计算法并编程实现,给出相关代码和结果分析。
二、模型的建立针对2023年数学建模国赛C题第三问,我们可以选择合适的模型进行建立。
在解决强连通分量问题时,Tarjan算法是一个较为经典并且优秀的选择。
Tarjan算法的基本思想是通过深度优先搜索,遍历所有的节点,并在遍历过程中对每个节点进行标记。
算法的具体步骤如下:1. 定义一个在搜索过程中用来记录节点状态的数组dfn[],初始化为0。
2. 定义一个辅助栈,用于存储已经访问过的节点。
3. 从图中任意一个节点开始进行深度优先搜索。
4. 在深度优先搜索的过程中,对每个节点进行标记。
首先将该节点的dfn值设为全局变量,然后将该节点压入辅助栈。
5. 对当前节点的所有出边进行遍历。
如果遍历到的下一个节点未被访问,则递归调用深度优先搜索函数。
6. 在访问下一个节点时,通过判断dfn[]和low[]的值,来判断是否需要将当前节点及其之前的节点从栈中弹出,并输出强连通分量。
7. 不断重复以上步骤,直到所有节点都被访问完为止。
三、求解方法根据上述模型的建立,我们可以通过编程实现Tarjan算法来求解2023年数学建模国赛C题第三问。
具体的代码实现如下:```python# 定义全局变量global dfn, low, indexdfn = [0] * nlow = [0] * nindex = 1# 定义深度优先搜索函数def dfs(u):global indexdfn[u] = indexlow[u] = indexindex += 1stack.append(u)for v in graph[u]:if dfn[v] == 0:dfs(v)low[u] = min(low[u], low[v]) elif v in stack:low[u] = min(low[u], dfn[v]) if dfn[u] == low[u]:component = []while True:node = stack.pop()component.append(node)if node == u:breakcomponents.append(component)# 初始化相关变量stack = []components = []# 对所有节点进行深度优先搜索for i in range(n):if dfn[i] == 0:dfs(i)# 输出所有的强连通分量for component in components:print(component)```通过以上的求解方法,我们可以得到给定有向图的所有强连通分量。
图的连通性判断算法
图的连通性判断算法图是离散数学中一个重要的概念,它由一组顶点和连接这些顶点的边组成。
在图理论中,连通性是一个基本的性质,它描述了图中是否存在一条路径将所有的顶点连接起来。
本文将介绍一些常用的图的连通性判断算法。
1. 深度优先搜索算法(DFS)深度优先搜索算法是一种经典的图遍历算法,也可以用于判断图的连通性。
该算法从一个起始顶点开始,沿着一条路径尽可能深入地搜索图,直到无法再继续下去。
然后回溯到上一个未访问的顶点,重复上述过程,直到所有的顶点都被访问过。
如果在搜索过程中,所有的顶点都被访问到,则图是连通的;否则,图是不连通的。
2. 广度优先搜索算法(BFS)广度优先搜索算法也是一种常用的图遍历算法,可以用于判断图的连通性。
该算法从一个起始顶点开始,按照广度优先的顺序逐层遍历与当前节点相邻的顶点。
如果在遍历过程中,所有的顶点都被访问到,则图是连通的;否则,图是不连通的。
3. 并查集算法并查集是一种用于解决"动态连通性"问题的数据结构,也可以用于判断图的连通性。
并查集通过维护一个森林(或称为集合)来表示各个顶点之间的关系,其中每个集合表示一个连通分量。
并查集提供了合并集合和查找集合的操作,通过这些操作可以判断图的连通性。
4. 可连通性矩阵可连通性矩阵是一种基于矩阵的图表示方法,用于判断图的连通性。
对于一个有n个顶点的图,可连通性矩阵是一个n×n的矩阵,其中第i行第j列的元素表示顶点i和顶点j之间是否存在一条路径。
如果对于所有的顶点对(i,j),可连通性矩阵中的元素都为1,则图是连通的;否则,图是不连通的。
5. 最小生成树算法最小生成树算法是用于求解连通图的一种常用算法,它通过选取图中的一些边来构建一棵树,该树包含图中的所有顶点,并且总权值最小。
如果最小生成树的边数等于顶点数减1,则原图是连通的;否则,原图是不连通的。
总结:本文介绍了几种常用的图的连通性判断算法,包括深度优先搜索算法、广度优先搜索算法、并查集算法、可连通性矩阵和最小生成树算法。
基于强连通分量深度优先搜索适用于开环供电的电网自动调电方法和
专利名称:基于强连通分量深度优先搜索适用于开环供电的电网自动调电方法和装置
专利类型:发明专利
发明人:陈益,宣俊楠,张永明,杨和俊,冯耀宇,徐宇,龚新勇,李磊,胡泽江,周瀛,赵灿辉,马力,李骞,徐肖庆,孙韬,李芳
洲,曹俊锋,陈炯,段燕青,杨柳,蒋正杰,童伟,杨鹏杰,甘
龙,唐强,余章,陈伟,高杉雪,雷昊,张建华,孙幸立,陈凯,
张南辉,阎定强,潘卫东,李滢洁,宋庆,张国斌,王杰鸿,
张晓思,李辉
申请号:CN201810986651.0
申请日:20180828
公开号:CN108988338A
公开日:
20181211
专利内容由知识产权出版社提供
摘要:本发明涉及一种基于强连通分量深度优先搜索适用于开环供电的电网自动调电方法,该方法包括步骤:步骤S1,通过采集供电网络的运行工况设置相应的有序元素实例化有向图。
步骤S2,采用深度优先搜索算法实时分析计算强连通分量。
步骤S3,根据运行方式改变的要求,采用深度优先搜索算法仿真计算改变运行方式改变后的全新强连通分量。
步骤S4,确认仿真结果可行后下发快速自动调电命令完成调电操作。
其中:有序元素包括:断路器,用来改变图结构;变压器,内部强连通分量;母线,强连通分量辅助参数,用来核对图结构。
申请人:云南电网有限责任公司昆明供电局
地址:650011 云南省昆明市官渡区拓东路63号昆明供电局
国籍:CN
代理机构:南京苏科专利代理有限责任公司更多信息请下载全文后查看。
通过深度优先搜索求强连通分量
基本定义:图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 -。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
基本定义:图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)大的强连通分量(这里可以这样理解,因为强连通是双向可达,所以转置对于强连通内部没有影响,对强连通分量之间,则指向关系发生了反转)。
这里关于图G的f(U)的大小问题,待会会做证明,我们先往下看。
现在我们证明,对于图G总是从f(U)大的强连通分量指向f(U)小的强连通分量,假设存在(u,v),u属于结点集C1,v属于结点集C2,因为u指向v,所以显然u.f>v.f(不理解的请参考上一篇博文:拓扑排序),并且,v在结点集C2中没有父节点,因此是f最大的节点,所以v.f=f(C2),因此f(C1)>u.f>v.f=f(C2),即f(C1)>f(C2).上述证明只是描绘出了原理要点,还有一系列诸如“图G与其转置Gt具有相同的强连通分量”之类比较显然的条件没有提及,不足之处,还请谅解!。