1.概述

理论中主要的遍历算法之一是DFS(深度优先搜索)。在本教程中,我们将介绍这种算法,并重点介绍以递归和非递归方式实现它。

首先,我们将解释DFS算法如何工作,看看如何如何?递归版本的样子。此外,我们还将提供一个示例来查看算法如何遍历给定的

接下来,我们将解释非递归版本背后的思想,展示其实现,并提供一个示例来展示两个版本如何处理示例图。

最后,我们将比较递归版本和非递归版本,并说明何时使用它们。

2.定义

让我们从定义DFS算法开始,并提供一个示例来了解算法是如何工作的。

2.1。一般概念

DFS算法从一个起始节点开始u。在每一步中,算法从邻接点中选择一个未访问的节点u并从该节点开始执行递归调用。当访问一个节点的所有邻居时,该节点的算法结束u并返回以检查发起节点调用的节点的邻居u

不像BFS算法,DFS不按级别的级别访问节点。相反,它尽可能深入地保持深处。一旦算法到达结束,它就尝试从上一个访问节点的其他呼道深入。因此,名称深度优先搜索源于这样一个事实,即算法试图在每一步中深入到图中。

为简单起见,我们将假设图形用邻接表数据结构。所讨论的算法都可以很容易地修改以适用于其他数据结构的情况。

2.2。例子

考虑以下图表示例:

假设我们从节点开始DFS操作年代。让我们假设邻接表内的所有邻近节点都按字母顺序递增排序。我们将执行以下操作:

  1. 年代,我们有两个邻居,一个,B。因此,我们继续一个
  2. 一个只有一个未访问的邻域F,我们从这个开始。
  3. 的节点F有两个没人拜访的邻居吗DE。因此,我们从节点继续D
  4. D就只有一个没来过的邻居吗B。所以,我们搬到了B
  5. 同样的,B只有一个未访问的邻域,是C。因此,下一个节点应该是C。请注意,B还有年代D相邻。但是,这些节点已经被访问过了。
  6. C只有E这是既无。因此,我们移到E
  7. 最后,节点C没有没人拜访的邻居。算法会缩回到C,B,D,F,一个, 然后年代。这些节点不会有任何未访问的邻接体。所以,算法就这样结束了。

结果,访问节点的最终顺序是{\黑体符号{S, A, F, D, B, C, E}}。

3.递归DFS

让我们介绍DFS算法的递归版本。看看实现:

呈现由QuickLaTeX.com

首先,我们定义访问了将被初始化的数组错误的值。使用\ boldsymbol{访问}数组用于确定哪些节点已被访问,以防止算法多次访问同一节点。

一旦访问了数组就绪,我们调用DFS函数。

DFS函数通过检查节点是否u是否被访问。如果是,它将返回而不执行任何操作。

然而,如果节点\ boldsymbol{你}未访问,则函数打印它。同样,函数标记节点u以防止在后面的步骤中被重新访问。

由于节点已处理,我们可以迭代其邻居。对于每一个,我们都称之为DFS递归函数。

递归版本的复杂性是\ boldsymbol {O (V + E)}, 在哪里V是节点数,和E是图中的边缘的数量。

复杂性背后的原因是,我们通过DFS函数。此外,对于每个节点,我们只遍历其邻居一次。因此,我们将在图中探讨图中的每个边缘两次。假设边缘在于uv,然后第一次,我们探索v作为u,而在第二次,我们发现了u作为v

因此,总而言之,内在为循环执行2 \ * E次了。

现在我们已经很好地理解了递归DFS版本,我们可以将其更新为迭代的(非递归的)。

4.迭代DFS.

让我们通过分析递归DFS版本来开始。从中,我们可以通过步骤构建迭代方法。

4.1。分析递归方法

读取递归DFS伪代码后,我们可以得到以下注意事项:

  1. 为每个节点u, DFS函数逐个查找相邻节点。但是,该函数只对未访问的对象执行递归调用。
  2. 当探测邻近节点时,DFS函数将探测的下一个节点是邻接表内第一个未访问的节点。这个命令必须得到尊重。
  3. 一旦DFS函数完成访问连接到节点的整个子图,它会终止并返回到上一个节点(在节点上执行递归调用的节点u)。
  4. 它的工作原理是程序将函数的当前参数和变量推入一个堆栈。一旦递归调用完成,程序将从堆栈中检索这些变量并继续执行。因此,我们需要使用手动堆栈将递归DFS更新为迭代版本。

让我们使用上面的注释来创建迭代版本。

4.2。理论观点

我们需要做的是模拟程序堆栈的工作。首先,让我们将起始节点推入堆栈。然后,我们可以执行多个步骤。

在每一步中,我们将从堆栈中提取一个元素。这应该类似于为DFS函数启动一个新的递归调用。一旦我们从堆栈中弹出一个节点,我们就可以迭代其邻居。

要访问提取的节点的呼应,我们也需要将它们推入堆栈中。但是,如果我们将节点推入堆栈然后提取下一个邻居,我们会得到最后一个邻居(因为堆栈作为首先换档)。

因此,我们需要将节点按它们在邻接表中出现的相反顺序添加到堆栈中。注意,只有在需要遵循递归版本相同的顺序时,才必须执行此步骤。如果我们不关心邻接表中节点的顺序,金宝搏官网188be那么我们也可以简单地将它们按任意顺序添加到堆栈中。

之后,我们将从堆栈中提取一个节点,访问它并遍历它的邻居。这个过程应该继续下去,直到堆栈变为空。

列出算法的主要思想后,我们可以组织一个整洁的迭代实施。

4.3。实现

让我们看一下迭代DFS的实现。

呈现由QuickLaTeX.com

与递归版本类似,我们将初始化数组访问了错误的值,该值将指示到目前为止我们是否访问了每个节点。然而,在迭代版本中,我们还初始化了一个空堆栈,它将模拟递归版本中的程序堆栈。

接下来,我们将起始节点压入堆栈。之后,我们将执行类似于递归版本的多个步骤。

在每个步骤中,我们从堆栈中提取一个节点,它模拟进入一个新的递归DFS调用。然后,我们检查是否已经访问过这个节点。如果我们这样做了,那么我们将继续不处理该节点,也不访问它的邻居。

但是,如果未访问该节点,我们将打印其值(指示我们处理的节点)并将其标记为访问。此外,我们遍历所有伴奏并将它们推向堆栈。将偶数推入堆栈模拟为每个的递归呼叫进行模拟。

尽管如此,在迭代版本中,我们必须以相反的顺序迭代邻居。因此,我们假设G (u)的邻接体u以逆转的顺序。

此外,我们将只推入未访问的节点,而不是将所有相邻节点都推入堆栈。这个步骤是不必要的,但它允许我们减少堆栈内节点的数量,而不影响结果。

由于迭代方法的步骤与递归的步骤相同,因此复杂性是\ boldsymbol {O (V + E)}, 在哪里V节点数是多少E是图中的边缘的数量。

5.例子

考虑以下图表示例:

让我们看看递归和非递归DFS版本是如何打印此图的节点的。

在递归DFS的情况下,我们在下面的示例中显示了前三个步骤:

注意,递归调用尚未结束的节点被标记为蓝色。

首先,我们从节点开始年代。我们探索它的邻居,因此移动到节点一个在第二步。在探索节点的邻居之后一个,我们继续到节点C

现在让我们看看接下来的三个步骤是怎样的:

在此示例中,我们有一些递归函数结束的节点。这些节点标有红色。

从节点C我们探索它的邻居B然后继续。

现在,事情变得棘手了。我们注意到B没有任何未访问的邻居。因此,我们在节点上完成递归调用B并返回节点C

然而,节点C也没有任何未访问的佐纪。因此,我们也完成了递归调用,并返回节点一个

我们注意到这个节点一个有一个未访问的邻居是节点D。因此,它从节点开始执行递归调用D。然而,节点D似乎没有任何未访问的节点。因此,函数返回到node一个

因为我们已经访问了node的所有邻接体一个,函数返回node年代。我们注意到节点年代没有更多未访问的节点。因此,函数停止。

因此,我们注意到访问的节点按顺序是{\ boldsymbol {s,a,c,b,d}}。

现在,让我们看看迭代方法将如何处理图形示例。

5.1。迭代DFS

考虑迭代DFS的前三个步骤:

在迭代DFS中,我们使用手动堆栈来模拟递归。堆栈标有蓝色。此外,到目前为止的所有访问节点都标有红颜色。

一开始,我们添加节点年代到堆栈的第一步。一旦我们弹出节点年代从堆栈中,它被访问。所以我们用红色做了标记。自从我们访问了node年代,我们把它所有的邻居按相反的顺序加到堆栈中。

接下来,我们的流行一个从堆栈中推动其所有不受检测的邻居DC。在这种情况下,我们添加节点D还是堆栈。这完全没问题,因为节点D是两者的邻居吗年代一个到现在还没人去过。

让我们来看看迭代方法的接下来三步:

我们流行节点C从堆栈中,将其标记为访问,并按下其相邻B

下一步是提取B马克被拜访过。但是,在本例中,是nodeB没有没人拜访的邻居。在这种情况下,我们没有把任何东西放入堆栈中。

我们弹出的下一个节点是nodeD。同样,我们已经访问了所有邻居,所以我们不会在其位置推动任何东西。

在我们访问了所有节点之后,我们仍然有节点BD在堆栈里面。所以,我们弹出这些节点并继续,因为我们已经访问了这两个节点。

请注意,我们与递归方法有类似的访问订单。访问的顺序也是{\ boldsymbol {s,a,c,b,d}}。

递归和迭代方法都具有相同的复杂性。因此,只需使用递归方法更容易,因为它更简单地实现。

然而,在极少数情况下,当程序的堆栈尺寸小时,我们可能需要使用迭代方法。原因是该程序在内部分配了手动堆栈空间,通常大于堆栈空间。

六,结论

在本教程中,我们介绍了深度第一搜索算法。首先,我们解释了算法如何运作和呈现递归版本的实现。

接下来,我们展示了如何嘲笑递归方法来实现它。

在此之后,我们逐步提供了这两种方法的示例,并比较了结果。

最后,我们总结了一个快速的比较,显示我们何时使用每种方法。

评论在本文上关闭!