BFS 广度优先搜索

Posted ProjectDaedalus

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了BFS 广度优先搜索相关的知识,希望对你有一定的参考价值。

This browser does not support music or audio playback. Please play it in WeChat or another browser.

图,一种可以表示对象复杂连接关系的数据结构。其较可以其他数据结构而言,可以很方便描述地图、电路、网络等拓扑结构。本文将介绍图的描述方法和基本的图搜索算法——BFS 广度优先搜索

BFS 广度优先搜索


定义

图(Graph),是由一组顶点(Vertex)和一组能够将两个顶点相连的边(Edge)所组成。具体地,根据边是否有方向,可划分为有向图、无向图;根据边是否有权重值,可划分为加权图、无加权图。如下图所示,其是一个顶点数V=5、边数E=6的无向无加权图G1(V,E)

BFS 广度优先搜索

[Note]:

  • 平行边:连接同一对顶点的两条边

  • 自环:一条顶点与自身连接的边

BFS 广度优先搜索

描述

现已无向无加权图G1(V,E)为例,介绍图的表示方法

1. 邻接矩阵

邻接矩阵Adjacency Matrix,是一个 VxV 矩阵,当元素 a(v,w) 值为1时,即表示顶点v到顶点w之间有一点边。易知,无向图的邻接矩阵是一个对称矩阵;对于加权图而言,邻接矩阵中表示某条边存在的元素 a(v,w) 可设为加权值。无向无加权图G1使用邻接矩阵描述如下:

BFS 广度优先搜索

缺点:

  • 当一个图很大(顶点数很多)而边数较少,此时若用邻接矩阵表示时,其矩阵是一个稀疏矩阵,且矩阵大小为VxV,这将大大浪费存储空间

  • 表示无向图时,由于是对称矩阵,存在冗余,浪费存储空间

  • 无法表示平行边

2.关联矩阵

关联矩阵 Incidence Matrix,是一个 VxE 矩阵,行表示顶点,列表示边。对于一个无向图而言,当某列中有2个元素的值均为1,即表示该边所连接的两个顶点。即,元素 a(v,w)、a(v,k) 值均为1时表示第v条边的两个顶点分别是w和k;对于一个有向图而言,元素 a(v,w)、a(v,k) 值分别为1、-1时,表示第v条边的起点、终点分别是w、k;当某列中,有一个元素的值为2,即可表示该顶点有一个自环;对于一个加权图而言,可以在关联矩阵下方再增加一行,用于表示各边的加权值。无向无加权图G1使用关联矩阵描述如下:

BFS 广度优先搜索

缺点:

  • 获取某个顶点的全部邻接点时,需要遍历图的所有边

3.邻接表

邻接表Adjacency List 是一个以顶点为索引的哈希表,然后该哈希表与每个顶点的邻接点列表相关联。无向无加权图G1使用邻接表描述如下:

BFS 广度优先搜索

BFS 算法

基本原理

对于图而言,广度优先搜索(BFS) 和 深度优先搜索(DFS) 是两种最基础的图搜索算法,这里介绍前者——BFS。该算法主要用于解决在一个无加权图下找到从起点S至指定点W的最短路径。该算法搜索路径首先从起点S开始,查找距离起点S一条边的顶点集合中是否存在顶点W;若没有则在所有距离起点S两条边的顶点集合中继续查找,直到找到顶点W。BFS算法的搜索图解过程,如下图所示,其过程类似于走迷宫,当走到分岔处,其会自动分裂成多个人,同时向多个分岔去前进搜索;而当多个分岔合并到一处时,亦会变为一个人继续前进搜索

算法流程

为保证先搜索相同距离长度的路径,该算法使用队列的FIFO特性来保存已经遇到但还未被搜索的分岔路径

  1. 将起点加入队列,并将该顶点状态标记为已搜索;重复Step 2 - 4,直到队列为空

  2. 从队列中取出一个顶点V

  3. 获取顶点V所有的未被搜索过的邻接点列表 toSearchList

  4. 对邻接点列表 toSearchList 中所有顶点执行:加入队列、状态标记为已搜索、记录访问该顶点路径的前驱顶点

Java Demo

现已上文图解的无向图为例,通过Java实现BFS算法

 
   
   
 
  1. /**

  2. * BFS

  3. */

  4. public class BFS {

  5. public static void BFSDemo() {


  6. Undigraph undigraph = getUndigraph(); // 通过邻接表构造一个无向图

  7. Integer vertexNum = undigraph.getVertexNum(); // 该无向图的顶点数

  8. Integer start = 0; // 指定遍历起点

  9. // BFS结果: 从起点到该点的最短路径上的上一个位置点

  10. // 即 previousVertex[v] = w, 意为 start → ... → w → v

  11. int[] previousVertex = new int[vertexNum];


  12. Boolean status = BFS(undigraph, start, previousVertex);

  13. if( status ) {

  14. visualPath(start, previousVertex);

  15. }

  16. }


  17. /**

  18. * 广度优先遍历

  19. * @param undigraph 图

  20. * @param start 起点

  21. * @return

  22. */

  23. private static Boolean BFS( Undigraph undigraph, Integer start, int[] previousVertex) {


  24. Integer vertexNum = undigraph.getVertexNum(); // 该无向图的顶点数


  25. Deque<Integer> deque = new LinkedList<>(); // 遍历队列

  26. int[] markArray = new int[vertexNum]; // 访问状态标记数组 0: 未访问, 1: 已访问


  27. // 将起点加入队列中

  28. deque.offer( start );

  29. markArray[start] = 1; // 标记状态为 已访问


  30. while( !deque.isEmpty() ) {

  31. Integer currentVertex = deque.poll(); // 从队列取出一个顶点


  32. // 该点无邻接点

  33. if( !undigraph.getAdjacencyList().containsKey(currentVertex) ) {

  34. return false;

  35. }

  36. // 取出该点的邻接点

  37. List<Integer> adjacencyList = undigraph.getAdjacencyList().get(currentVertex);

  38. for( Integer nextVertex : adjacencyList) {

  39. if( markArray[nextVertex] != 1 ) { // 当前点的邻接点未访问,则进行访问

  40. markArray[nextVertex] = 1; // 标记状态为: 已访问

  41. deque.offer( nextVertex ); // 将邻接点加入队列

  42. previousVertex[nextVertex] = currentVertex; // 记录访问路径

  43. }

  44. }

  45. }

  46. return true;

  47. }


  48. /**

  49. * BFS 结果可视化

  50. * @param start 起点

  51. * @param resultPath BFS结果路径

  52. */

  53. private static void visualPath(Integer start, int[] resultPath) {

  54. for( int i=0; i<resultPath.length; i++ ) {

  55. List<String> path = new LinkedList<String>();

  56. Integer vertex = i;

  57. while (vertex != start) {

  58. path.add( vertex.toString() );

  59. vertex = resultPath[vertex];

  60. }

  61. path.add(vertex.toString());

  62. Collections.reverse(path);

  63. String pathStr = path.stream().collect( Collectors.joining("->") );

  64. System.out.println(pathStr);

  65. }

  66. }


  67. /**

  68. * 根据邻接表构造无向图

  69. * @return

  70. */

  71. private static Undigraph getUndigraph() {

  72. Map<Integer, List<Integer>> adjacencyList = new HashMap<>();


  73. adjacencyList.put(0, Arrays.asList(1, 2, 5) );

  74. adjacencyList.put(1, Arrays.asList(0, 2) );

  75. adjacencyList.put(2, Arrays.asList(0, 1, 3, 4) );

  76. adjacencyList.put(3, Arrays.asList(2, 4, 5) );

  77. adjacencyList.put(4, Arrays.asList(2, 3) );

  78. adjacencyList.put(5, Arrays.asList(0, 3) );


  79. Undigraph undigraph = new Undigraph();

  80. undigraph.setAdjacencyList( adjacencyList );

  81. undigraph.setVertexNum( adjacencyList.size() );

  82. return undigraph;

  83. }

  84. }


  85. /**

  86. * 无向图

  87. */

  88. @Data

  89. class Undigraph {

  90. /**

  91. * 邻接表

  92. */

  93. private Map<Integer, List<Integer>> adjacencyList;

  94. /**

  95. * 顶点数

  96. */

  97. private Integer vertexNum;

  98. }

算法结果:

应用

  • 无加权图的最短路径问题

  • 二分图判定问题

参考文献

  1. 算法(第4版) Robert Sedgewick、Kevin Wayne 著

以上是关于BFS 广度优先搜索的主要内容,如果未能解决你的问题,请参考以下文章

广度优先搜索(BFS)的一个(重要!)细节。

DFS-深度优先搜索与BFS-广度优先搜索

基本算法——深度优先搜索(DFS)和广度优先搜索(BFS)

广度优先搜索(BFS)----------------(TjuOj1140_Dungeon Master)

面试刷题:广度优先搜索BFS | 第91期

使用BFS(广度优先搜索)解迷宫类问题