美文网首页
图遍历算法之DFS/BFS

图遍历算法之DFS/BFS

作者: lilyblspku | 来源:发表于2019-05-16 15:31 被阅读0次

在计算机科学, 图遍历(Tree Traversal,也称图搜索)是一系列图搜索的算法, 是单次访问树结构类型数据(tree data structure)中每个节点以便检查或更新的一系列机制。图遍历算法可以按照节点访问顺序进行分类,根据访问目的或使用场景的不同,算法大致可分为28种:

序号 算法名称 应用场景
1 \alpha-\beta 修剪算法 减少树搜索过程中minimax算法评估的节点数量,属于对抗性搜索算法
2 A^{\star}算法 用于寻路和图遍历,在多个节点之间寻找路径,多用于点对点
3 B^{\star} 算法 一种最佳优先图搜索算法,用于查找给定初始节点到任何目标节点的最低成本路径
4 回溯算法(backtracking) 通用算法,用于查找某些计算问题的所有可行解,特别是约束满足问题,逐步构建候选解,并在确定候选解不可行时进行回溯
5 波束搜索(beam) 算法 启发式搜索算法,通过扩展有限集中最可能的节点来探索图形
6 Bellman-Ford 算法 计算加权有向中单个源节点到其他节点的最短路径,比Dijkstra算法慢,但更通用
7 Best-first 算法 根据指定规则选择最可能的点来进行图搜索
8 双向(Bidirectional)搜索 从有向图中找出给定初始点到目标点的最短路径,进行两次搜索:一次从初始点开始向前搜索,一次从目标点向后搜索,相遇时停止
9 Boruvka算法 贪婪算法,从图中找到所有边缘权重不同的最小生成树或最小生成林
10 分支定界(Branch & Bound) 算法 通过状态空间搜索可行解
11 广度优先搜索BFS (Breadth-first search) 遍历或搜索树或图数据结构,从图任意节点开始,并在移动到下一节点之前探索当前深度水平的所有相邻节点
12 D^{\star}算法 增量搜索算法
13 深度优先搜索DFS(Depth-first search) 遍历或搜索树或图数据结构的算法,选择任意节点作为根节点,在回溯之前尽可能地沿着每个分支进行搜索
14 Dijkstra算法(SPF 算法, shortest path faster) 搜索节点之间的最短路径。单源最短路径搜索:给定初始点,搜寻初始点到其他点的最短路径,生成最短路径树
15 Edmonds算法 最小生成树的定向模拟
16 Floyd-Warshall算法 在具有正或负边缘权重的加权图中寻找最短路径
17 Fringe搜索 寻找从给定初始节点到目标节点之间的最低成本路径
18 爬山(Hill Climbing) 算法 从图的任意节点开始,然后尝试通过对节点进行增量更改来找到更好的节点
19 IDA (Iterative deepening A^{\star})算法 在加权图中找到从给定初始节点到一组目标节点中任意节点之间的最短路径
20 迭代深化搜索算法(Iterative deepening) 具有深度限制的深度优先搜索
21 Johnson算法 在稀疏边缘加权有向图中找到节点之间最短路径
22 跳跃点搜索JPS (Jump point)算法 A^{\star}算法优化版本
23 Kruskal 算法 最小生成树算法,找到联通树中任意两棵树的最小权重边缘
24 词典宽度有限搜索Lex-BFS算法 对图节点进行排序的线形时间搜索算法
25 LPA^{\star}算法 基于A^{\star}算法的增量启发式搜索算法
26 Prim 算法 一种贪婪算法,用于从加权无向图中找到最小生成树
27 SMA^{\star}算法 使用有界内存的A^{\star}算法,继承了A^{\star}算法的特性
28 最短路径快速SPFA算法 Bellman-Ford算法的改进,在加权有向图中计算单源最短路径

图遍历即以特定方式访问图中所有节点,给定节点下有多种可能的搜索路径。假定以顺序方式进行(非并行),还未访问的节点就需通过堆栈(LIFO)或队列(FIFO)规则来确定访问先后。由于树结构是一种递归的数据结构,在清晰的定义下,未访问节点可存储在调用堆栈中。本文介绍了图遍历领域最流行的广度优先搜索算法BFS和深度优先搜索算法DFS,对其原理、应用及实现进行了阐述。通常意义上而言,深度优先搜索(DFS)通过递归调用堆栈比较容易实现,广义优先搜索通过队列实现。

一、深度优先搜索(Depth-first search)

深度优先搜索(DFS)是用于遍历或搜索图数据结构的算法,该算法从根节点开始(图搜索时可选择任意节点作为根节点)沿着每个分支进行搜索,分支搜索结束后在进行回溯。在进入下一节点之前,树的搜索尽可能的加深。
DFS的搜索算法如下(以二叉树为例):假定根节点(图的任意节点可作为根节点)标记为N,
(L) : 递归遍历左子树,并在节点N结束。
(R): 递归遍历右子树,并在节点N结束。
(N): 访问节点N
这些步骤可以以任意次序排列。如果(L)在(R)之前,则该过程称为从左到右的遍历;反之,则称为从右到左的遍历。根据访问次序的不同,深度优先搜索可分为 pre-order、in-order、out-order以及post-order遍历方式。

1. Pre-order(NLR)遍历

(a)检查当前节点是否为空;
(b)展示根节点或当前节点数据;
(c)递归调用pre-order函数遍历左子树;
(d)递归调用pre-order函数遍历右子树。
pre-order遍历属于拓扑排序后的遍历,父节点总是在任何子节点之前被访问。该遍历方式的图示如下:


图1. pre-order遍历

遍历次序依次为:F -B -A-D- C-E-G- I-H.

2. In-order (LNR)遍历

(a)检查当前节点是否为空;
(b)递归调用in-order函数遍历左子树;
(c)展示根节点或当前节点数据;
(d)递归调用in-order函数遍历右子树。
在二叉树搜索中,in-order遍历以排序顺序访问节点数据。该遍历方式的图示如下:


图2. in-order遍历

遍历次序依次为:A -B - C - D - E - F - G -H-I

3. Out-order (RNL)遍历

(a)检查当前节点是否为空;
(b)递归调用out-order函数遍历右子树;
(c)展示根节点或当前节点数据;
(d)递归调用out-order函数遍历左子树。
该遍历方式与LNR类似,但先遍历右子树后遍历左子树。仍然以图2为例,遍历次序依次为:H- I-G- F- B- E- D- C- A.

4. post-order (LRN)遍历

(a)检查当前节点是否为空;
(b)递归调用post-order函数遍历左子树;
(c)递归调用post-order函数遍历右子树;
(d)展示根节点或当前节点数据。
post-order遍历图示如下:


图3. post-order遍历

遍历次序依次为:A-C-E-D-B-H-I-G-F.

pre-order遍历方式使用场景:用于创建树或图的副本;
in-order遍历使用场景:二叉树遍历;
post-order遍历使用场景:删除树

遍历追踪也称树的序列化,是所访问根节点列表。无论是pre-order,in-order或是post-order都无法完整的描述树特性。给定含有不同元素的树结构,pre-order或post-order与in-order遍历方式结合起来使用才可以描述树的独特性。

二、广度优先搜索(Breath-first search)

树或图形的访问也可以按照节点所处的级别进行遍历。在每次访问下一层级节点之前,遍历所在高层级的所有节点。BFS从根节点(图的任意节点可作为根节点)出发,在移动到下一节点之前访问所有相同深度水平的相邻节点。

1945年,Konrad Zuse 在他被拒的博士论文中首次提到BFS在联通子图方面的应用。1972年,该博士论文被出版。1959年,Edward F. Moore对BFS进行重新设计,并用于寻找迷宫中的最短路径。 1961年,C.Y.Lee对算法进行开发作为电路路由算法。

BFS的遍历方法图示如下:


图4. BFS遍历

遍历次序依次为: F-B-G-A-D-I-C-E-H.

三、DFS与BFS的伪代码实现

1. DFS: pre-order 遍历

preorder(node)
  if (node = null)
    return
  visit(node)
  preorder(node.left)
  preorder(node.right)
iterativePreorder(node)
  if (node = null)
    return
  s ← empty stack
  s.push(node)
  while (not s.isEmpty())
    node ← s.pop()
    visit(node)
    //right child is pushed first so that left is processed first
    if (node.right ≠ null)
      s.push(node.right)
    if (node.left ≠ null)
      s.push(node.left)

2. DFS:in-order遍历

inorder(node)
  if (node = null)
    return
  inorder(node.left)
  visit(node)
  inorder(node.right)
iterativeInorder(node)
  s ← empty stack
  while (not s.isEmpty() or node ≠ null)
    if (node ≠ null)
      s.push(node)
      node ← node.left
    else
      node ← s.pop()
      visit(node)
      node ← node.right

3. DFS: post-order遍历

postorder(node)
  if (node = null)
    return
  postorder(node.left)
  postorder(node.right)
  visit(node)
iterativePostorder(node)
  s ← empty stack
  lastNodeVisited ← null
  while (not s.isEmpty() or node ≠ null)
    if (node ≠ null)
      s.push(node)
      node ← node.left
    else
      peekNode ← s.peek()
      // if right child exists and traversing node
      // from left child, then move right
      if (peekNode.right ≠ null and lastNodeVisited ≠ peekNode.right)
        node ← peekNode.right
      else
        visit(peekNode)
        lastNodeVisited ← s.pop()

4. BFS

levelorder(root)
  q ← empty queue
  q.enqueue(root)
  while (not q.isEmpty())
    node ← q.dequeue()
    visit(node)
    if (node.left ≠ null)
      q.enqueue(node.left)
    if (node.right ≠ null)
      q.enqueue(node.right)

四、DFS与BFS的R及Python代码实现

1. R语言实现DFS与BFS

图算法相关的R包为igraph,主要包括图的生成、图计算等一系列算法的实现。

1.1 R语言实现DFS:函数dfs

使用方法:

dfs(graph, root, neimode = c("out", "in", "all", "total"),
unreachable = TRUE, order = TRUE, order.out = FALSE,
father = FALSE, dist = FALSE, in.callback = NULL,
out.callback = NULL, extra = NULL, rho = parent.frame())

参数说明:

参数 含义
graph 输入图
root 根节点,搜索开始的单个节点
neimode 对有向图,指定访问边的类型。'out' 表示从节点出发的边,'in'表示进节点的边,'all' 完全忽略边的方向,'total' 与'all'相同。对于无向图,该参数可忽略
unreachable 逻辑值,取值'true'或'false', 表示搜索是否需要访问从根节点无法到达的节点集合
order 逻辑值,是否返回DFS排序后的节点序列
order.out 逻辑值,是否返回离开子图的节点排序
father 逻辑值,是否返回节点的父节点
dist 逻辑值,是否返回从根节点出发搜索的距离
in.callback 若不为空,则存在callback函数
out.callback 若不为空,则存在callback函数
extra callback函数的其他参数
rho callback函数被评估的环境

示例:

require(igraph)
## generate graph
graph <- make_tree(10) %du% make_tree(10)
# plot graph
plot(graph)
# 搜索从根节点出发的节点排序
dfs_res<-dfs(graph, root=1, "out",TRUE, TRUE, TRUE, TRUE)

结果展示:


图5. 无向图示例

DFS R输出节点排序:

1-2-4-8-9-5-10-3-6-7-11-12-14-18-19-15-20-13-16-17

1.2 R语言实现BFS:函数bfs

使用方法:

bfs(graph, root, neimode = c("out", "in", "all", "total"),
unreachable = TRUE, restricted = NULL, order = TRUE,
rank = FALSE, father = FALSE, pred = FALSE, succ = FALSE,
dist = FALSE, callback = NULL, extra = NULL,
rho = parent.frame())

参数含义同dfs
示例:

require(igraph)
## generate graph
graph <- make_ring(10) %du% make_ring(10)
# plot graph
plot(graph)
# 搜索从根节点出发的节点排序
bfs_res<-bfs(graph, root=1, "out",order=TRUE, rank=TRUE, father=TRUE, pred=TRUE,
             succ=TRUE, dist=TRUE)

结果展示:


图6. 无向图示例

BFS R输出节点排序:

1-2-10-3-9-4-8-5-7-6-11-12-20-13-19-14-18-15-17-16

2. Python 实现BFS 及DFS

以寻找两点之间的路径为例,分别展示BFS及DFS的实现。图示例如下:


图7. 无向图示例

2.1 Python实现BFS

示例:

graph = {'A': set(['B', 'C']),
         'B': set(['A', 'D', 'E']),
         'C': set(['A', 'F']),
         'D': set(['B']),
         'E': set(['B', 'F']),
         'F': set(['C', 'E'])}

def bfs_paths(graph, start, goal):
    queue = [(start, [start])]
    while queue:
        (vertex, path) = queue.pop(0)
        for next in graph[vertex] - set(path):
            if next == goal:
                yield path + [next]
            else:
                queue.append((next, path + [next]))

list(bfs_paths(graph, 'A', 'F'))

输出结果:

[['A', 'C', 'F'], ['A', 'B', 'E', 'F']]

2.2 Python实现DFS

示例:

graph = {'A': set(['B', 'C']),
         'B': set(['A', 'D', 'E']),
         'C': set(['A', 'F']),
         'D': set(['B']),
         'E': set(['B', 'F']),
         'F': set(['C', 'E'])}

def dfs_paths(graph, start, goal):
    stack = [(start, [start])]
    while stack:
        (vertex, path) = stack.pop()
        for next in graph[vertex] - set(path):
            if next == goal:
                yield path + [next]
            else:
                stack.append((next, path + [next]))

list(dfs_paths(graph, 'A', 'F'))

输出结果:

[['A', 'C', 'F'], ['A', 'B', 'E', 'F']]

参考资料:

[1] 维基百科:https://en.wikipedia.org/wiki/Tree_traversal
[2] GeeksforGeeks: https://www.geeksforgeeks.org/tree-traversals-inorder-preorder-and-postorder/
[3] http://webdocs.cs.ualberta.ca/~holte/T26/tree-traversal.html
[4]Martin Broadhurst, Graph Algorithm: http://www.martinbroadhurst.com/Graph-algorithms.html#section_1_1
[5]igraph: https://igraph.org/r/doc/dfs.html
[6]igraph: https://igraph.org/r/doc/bfs.html
[7] Depth-First Search and Breadth-First Search in Python: https://eddmann.com/posts/depth-first-search-and-breadth-first-search-in-python/

相关文章

  • 图的桥和割点

    内容概要: 图中的桥 图的DFS遍历树和BFS遍历树及其与寻桥算法的关系 图的割点 DFS和BFS的对比小结 桥(...

  • 图遍历算法之DFS/BFS

    在计算机科学, 图遍历(Tree Traversal,也称图搜索)是一系列图搜索的算法, 是单次访问树结构类型数据...

  • 图论小结

    图的存储 邻接矩阵 邻接表 图的遍历 DFS(双向DFS) BFS(剪枝) 单源最短路径算法 dij 条件:无负权...

  • 无向图DFS和BFS

    基本结构 DFS深度优先遍历 BFS广度优先遍历 符号图

  • leecode岛屿数量

    题目描述可用解法DFS 深度优先遍历BFS 广度优先遍历算法思路:下列代码用BFS,循环遍历输入的二维列表如果遇到...

  • (原创)BFS广度优先算法,看完这篇就够了

    BFS算法 上一篇文章讲解了DFS深度优先遍历的算法,我们说 DFS 顾名思义DEEPTH FIRET,以深度为第...

  • BFS

    [TOC] BFS 和 DFS BFS广度有限搜索和DFS深度优先搜索算法是特别常用的两种算法 DFS 算法就是回...

  • 岛屿问题

    岛屿系列题目的核心考点就是用 DFS/BFS 算法遍历二维数组。本文分析DFS算法。 一、框架 因为二维矩阵本质上...

  • 690. Employee Importance

    690. Employee Importance【思路】: 遍历BFS: DFS:

  • 图的BFS & DFS & Dijkstra算法

    python 队列实现的图的BFS,类似于哈夫曼树遍历: 栈实现的图的DFS: BFS扩展最短路径: Dijkst...

网友评论

      本文标题:图遍历算法之DFS/BFS

      本文链接:https://www.haomeiwen.com/subject/txzpaqtx.html