Depth-First Search(深度优先搜索)

在上一篇文章,我们共同探讨了广度优先搜索算法(BFS),在遍历下一层顶点之前,需要获取它的所有邻居顶点。在这篇文章里,我们将用另一种 depth-first search(DFS) 去遍历一个树。
DFS算法有很多的实际应用:

  • 拓扑排序。
  • 检测是否有环。
  • 寻找路径,例如迷宫拼图中的路径。
  • 在图中查找两个顶点是否相连。
    在 DFS中,从一个给定的顶点开始,然后沿着它的边一直查找,直到到达该路线的尽头。为了实现这一点,我们将使用 Stack 来探索另一条边的下一个可达的顶点,直到访问完所有顶点。

示例

DFS1.png

我们将使用 stacklast-in-last-out的特性,去跟踪每一层的顶点。每一次向 stackpush 顶点,意味着遍历深入了一层。当我们一直push到最底端的时候,可以pop弹出上一层的顶点。
DFS2.png

1,我们选择任意顶点A作为起点,并将它加入到栈中。
2,只要stack不是空的,我们就访问栈顶的顶点元素,并且将它们第一个没有访问过的元素入栈,在这个例子中,访问顶点A,并且将顶点B入栈。
DFS3.png

1,接下来,访问顶点B,因为,A已经访问过了,所以,将E入栈。
2,访问顶点E,并将顶点F入栈。

值得注意的是,每一次往栈中push元素,就相当于我们遍历更深了一层。我们只需要沿着一条路径一直访问下去,直到到达最底部。然后进行回溯。而不是访问所有的相邻顶点。


DFS4.png

1,我们访问顶点 F ,并且将顶点 G 入栈。
2,我们访问顶点 G ,并且将顶点 C 入栈


DFS5.png

1,接下来访问顶点C,它的邻居顶点为 [A, F, G],但是它们都已经被访问过了,所以该线路已经到达了尽头。这时候,我们就可以使栈出栈,来进行往上一层进行回溯。
2,通过1操作,我们就回到了顶点G,顶点G的邻居顶点为 [F, C],但是都已经访问过了,所以,我们需要使 G 出栈。
DFS6.png

1,F的邻居都已经访问过了,接下来将F出栈。
2,现在我们回到了顶点E的位置,E的邻居H还没有访问过,所以将H入栈。


DFS7.png

1,访问H顶点,H顶点没有下一层了,所以将H出栈。
2,顶点E的邻居顶点都被访问过了,下一步,将 E 出栈。


DFS8.png

1,B和E的情况一样,所以将B出栈。
2,栈里就剩下A了,这样我们就回到了顶点A,顶点A的邻居 D还没有访问过,所以将D入栈。
DFS9.png

1,访问顶点D,顶点D没有下一层,已经到最后面了,将D出栈,

2,我们再一次回到顶点A,A的邻居顶点都已经访问过了,将顶点A出栈,此时,栈已经空了,DFS遍历结束。
通过以上对顶点的遍历,我们可以构建出类似树的数据结构,DFS遍历形成的树会比较高一些。


DFS10.png

实现

extension Graph where Element: Hashable {

  func depthFirstSearch(from source: Vertex)
      -> [Vertex] {
    var stack: Stack> = []
    var pushed: Set> = []
    var visited: [Vertex] = []

    stack.push(source)
    pushed.insert(source)
    visited.append(source)

    // more to come ...

    return visited
  }
}

在以上代码中,我们定义了 depthFirstSearch(from:) 函数,传入一个顶点作为遍历的起点。并且,返回一个有序数组,来存放已经存放的顶点。在这里,我们使用了三种数据结构:
1,stack 用来存储我们遍历顶点的路径。
2,pushed 用来记录哪些顶点已经访问过了,防止同一个节点访问两次,我们使用Set 来确保 最快的查找速度。
3,visited 是一个数组,用来按顺序存储已访问的顶点。

接下来,在上面的代码部分完善该方法:

outer: while let vertex = stack.peek() { // 1
  let neighbors = edges(from: vertex) // 2
  guard !neighbors.isEmpty else { // 3
    stack.pop()
    continue
  }
  for edge in neighbors { // 4
    if !pushed.contains(edge.destination) {
      stack.push(edge.destination)
      pushed.insert(edge.destination)
      visited.append(edge.destination)
      continue outer // 5
    }
  }
  stack.pop() // 6
}

该方法做了以下操作:
1,当stack不为空时,我们需要一直检查栈顶的元素。并将该循环定义为 outer
2,查找当前访问顶点的所有邻居节点。
3,如果当前节点没有邻居节点,出栈,继续访问下一个顶点。
4,对邻居顶点进行遍历,如果邻居顶点中含有未被访问的顶点,就将未访问的顶点入栈。
5,从邻居顶点中,找到了未访问过的顶点,然后继续 outer循环。

当 栈为空时,DFS算法就完成了。最后就是将有序存放顶点的数组返回。

最后附上本文的相关代码DataAndAlgorim

参考链接《Data Structures & Algorithms in Swift》

你可能感兴趣的:(Depth-First Search(深度优先搜索))