什么广度优先搜索(BFS) 和树(Tree)的数据结构一样,我们也可以访问图(Graph)的所有节点。
广度优先搜索(breadth-first search,BFS)是搜索图的算法之一。
图遍历可以用来寻找特定的顶点或寻找两个顶点之间的路径,检查图是否连通,检查图是否含有环,等等。
广度优先搜索会指定一个顶点,从这个顶点开始遍历图结构,遍历时先访问这个顶点的相邻顶点,然后再按队列的顺序访问相邻顶点的下一层顶点,直到到达要搜索的目标或者搜索完所有的点。
广度优先搜索是”先宽后深地访问顶点”的图搜索算法。
广度优先搜索(BFS)全过程 例如用下面的图结构,从顶点A开始搜索,直到搜索到顶点G。
可以知道,顶点A的相邻顶点有顶点B、顶点C、顶点D,这三个顶点,将它们视为开始搜索的顶点,加入队列。
根据队列数据结构(先进先出-FIFO)的原则,我们从顶点A的三个相邻顶点(B-C-D)中,选择最先进入队列的顶点B,移动到顶点B。
获取顶点B的相邻顶点(E-F),将他们加入队列。
根据队列的顺序,使用同样的方式:
到达顶点C,获取顶点C的相邻顶点(H),将它加入队列,接着(根据队列先进先出的原则)移动到顶点D。
获取顶点D的相邻顶点(I-J),将它加入队列,接着(根据队列先进先出的原则)移动到顶点E。
获取顶点E的相邻顶点(K),将它加入队列,接着(根据队列先进先出的原则)移动到顶点F。
获取顶点F的相邻顶点,发现没有相邻顶点,则进行下一个顶点H的出列。
获取顶点H的相邻顶点(G),将它加入队列,接着(根据队列先进先出的原则)移动到顶点I。
获取顶点I的相邻顶点,发现没有相邻顶点,则进行下一个顶点J的出列。
获取顶点J的相邻顶点(L),将它加入队列,接着(根据队列先进先出的原则)移动到顶点K。
获取顶点K的相邻顶点,发现没有相邻顶点,则进行下一个顶点G的出列。
获取顶点G,找到需要到达的目标,结束搜索。
实现广度优先搜索(BFS) 代码流程
为了标记图的顶点,我们需要提供一个表示顶点颜色的常量对象。
白色(WHITE):表示该顶点还没有被访问
灰色(GREY):表示该顶点被访问过,但并未被探索过
黑色(BLACK):表示该顶点被访问过且被完全探索过
1 2 3 4 5 6 const Colors = { WHITE : 0 , GREY : 1 , BLACK : 2 };
1 2 3 4 5 6 7 8 9 10 11 12 const initializeColor = vertices => { const color = {}; for (let i = 0 ; i < vertices.length ; i++) { color[vertices[i]] = Colors .WHITE ; } return color; };
接着便是广度优先搜索算法的完整代码思路:
声明广度优先搜索算法的函数-breadthFirstSearch,接收三个参数,分别是graph(要遍历的图实例)、startVertex(开始顶点)、callback(回调函数)
在函数内获取传入图实例(graph)的顶点列表、邻接表,并将图的顶点列表中的所有顶点初始化颜色为白色(表示该顶点还没有被访问)
创建一个队列数据结构实例,并将startVertex(开始顶点)入队列。
如果队列不为空,则进入循环,循环体内执行以下步骤:
根据入队顺序,取出队列中的一个顶点,并获取这个顶点的邻接表,并将这个顶点的颜色标识为灰色(该顶点被访问过,但并未被探索过)
如果这个顶点的邻接表不为空,则遍历这个顶点的邻接表中的顶点,遍历时,如果该邻接表的顶点是白色,则将它标识为灰色(该顶点被访问过,但并未被探索过),并将该邻接表顶点加入队列。
这个顶点的邻接表探索完后,将这个顶点标识为黑色(已被访问且被完全探索)
如果有传入回调函数,则执行该回调函数,回调函数会将这个顶点作为入参方法。
代码实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 export const breadthFirstSearch = (graph, startVertex, callback ) => { const vertices = graph.getVertices (); const adjList = graph.getAdjList (); const color = initializeColor (vertices); const queue = new Queue (); queue.enqueue (startVertex); while (!queue.isEmpty ()) { const u = queue.dequeue (); const neighbors = adjList.get (u); color[u] = Colors .GREY ; for (let i = 0 ; i < neighbors.length ; i++) { const w = neighbors[i]; if (color[w] === Colors .WHITE ) { color[w] = Colors .GREY ; queue.enqueue (w); } } color[u] = Colors .BLACK ; if (callback) { callback (u); } } };
代码用例 我们初始化一个图,用于测试广度优先搜索。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 let graph = new Graph ();const myVertices = ['A' , 'B' , 'C' , 'D' , 'E' , 'F' , 'G' , 'H' , 'I' ];for (let i = 0 ; i < myVertices.length ; i++) { graph.addVertex (myVertices[i]); } graph.addEdge ("A" , "B" ); graph.addEdge ("A" , "C" ); graph.addEdge ("A" , "D" ); graph.addEdge ("C" , "D" ); graph.addEdge ("C" , "G" ); graph.addEdge ("D" , "G" ); graph.addEdge ("D" , "H" ); graph.addEdge ("B" , "E" ); graph.addEdge ("B" , "F" ); graph.addEdge ("E" , "I" );
将这个实例图,用广度优先搜索算法打印出来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const printVertex = (value ) => console .log ('Visited vertex: ' + value);breadthFirstSearch (graph, vertices[0 ], printVertex);
广度优先搜索算法(BFS)还可以做更多 广度优先搜索算法的工作原理了解之后,它还可以做更多事情,比如使用它来搜索图顶点的最短访问路径,或者图中某个顶点的前溯点。
下面是一段利用BFS探索图实例中每个顶点的最短路径 和前溯点 。
代码实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 export const BFS = (graph, startVertex ) => { const vertices = graph.getVertices (); const adjList = graph.getAdjList (); const color = initializeColor (vertices); const queue = new Queue (); const distances = {}; const predecessors = {}; queue.enqueue (startVertex); for (let i = 0 ; i < vertices.length ; i++) { distances[vertices[i]] = 0 ; predecessors[vertices[i]] = null ; } while (!queue.isEmpty ()) { const u = queue.dequeue (); const neighbors = adjList.get (u); color[u] = Colors .GREY ; for (let i = 0 ; i < neighbors.length ; i++) { const w = neighbors[i]; if (color[w] === Colors .WHITE ) { color[w] = Colors .GREY ; distances[w] = distances[u] + 1 ; predecessors[w] = u; queue.enqueue (w); } } color[u] = Colors .BLACK ; } return { distances, predecessors }; };
测试用例 我们初始化一个图,用于测试广度优先搜索。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 let graph = new Graph ();const myVertices = ['A' , 'B' , 'C' , 'D' , 'E' , 'F' , 'G' , 'H' , 'I' ];for (let i = 0 ; i < myVertices.length ; i++) { graph.addVertex (myVertices[i]); } graph.addEdge ("A" , "B" ); graph.addEdge ("A" , "C" ); graph.addEdge ("A" , "D" ); graph.addEdge ("C" , "D" ); graph.addEdge ("C" , "G" ); graph.addEdge ("D" , "G" ); graph.addEdge ("D" , "H" ); graph.addEdge ("B" , "E" ); graph.addEdge ("B" , "F" ); graph.addEdge ("E" , "I" );
将图实例传入BFS,探索图实例中每个顶点的最短路径 和前溯点 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 const shortestPathA = BFS (graph, myVertices[0 ]);console .log (shortestPathA.distances );console .log (shortestPathA.predecessors );
通过前溯点数组,我们还可以构建从顶点 A 到其他顶点的路径。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 const fromVertex = myVertices[0 ]; for (let i = 1 ; i < myVertices.length ; i++) { const toVertex = myVertices[i]; const path = new Stack (); for ( let v = toVertex; v !== fromVertex; v = shortestPathA.predecessors [v] ) { path.push (v); } path.push (fromVertex); let s = path.pop (); while (!path.isEmpty ()) { s += ' - ' + path.pop (); } console .log (s); }
完整代码实现 附上上述所有代码的完整实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 import { Queue } from '/Queue/app.js' ;import { Graph } from '/Graph/app.js' ;const Colors = { WHITE : 0 , GREY : 1 , BLACK : 2 };const initializeColor = vertices => { const color = {}; for (let i = 0 ; i < vertices.length ; i++) { color[vertices[i]] = Colors .WHITE ; } return color; };export const breadthFirstSearch = (graph, startVertex, callback ) => { const vertices = graph.getVertices (); const adjList = graph.getAdjList (); const color = initializeColor (vertices); const queue = new Queue (); queue.enqueue (startVertex); while (!queue.isEmpty ()) { const u = queue.dequeue (); const neighbors = adjList.get (u); color[u] = Colors .GREY ; for (let i = 0 ; i < neighbors.length ; i++) { const w = neighbors[i]; if (color[w] === Colors .WHITE ) { color[w] = Colors .GREY ; queue.enqueue (w); } } color[u] = Colors .BLACK ; if (callback) { callback (u); } } };let graph = new Graph ();const myVertices = ['A' , 'B' , 'C' , 'D' , 'E' , 'F' , 'G' , 'H' , 'I' ];for (let i = 0 ; i < myVertices.length ; i++) { graph.addVertex (myVertices[i]); } graph.addEdge ("A" , "B" ); graph.addEdge ("A" , "C" ); graph.addEdge ("A" , "D" ); graph.addEdge ("C" , "D" ); graph.addEdge ("C" , "G" ); graph.addEdge ("D" , "G" ); graph.addEdge ("D" , "H" ); graph.addEdge ("B" , "E" ); graph.addEdge ("B" , "F" ); graph.addEdge ("E" , "I" );const printVertex = (value ) => console .log ('Visited vertex: ' + value);breadthFirstSearch (graph, vertices[0 ], printVertex);export const BFS = (graph, startVertex ) => { const vertices = graph.getVertices (); const adjList = graph.getAdjList (); const color = initializeColor (vertices); const queue = new Queue (); const distances = {}; const predecessors = {}; queue.enqueue (startVertex); for (let i = 0 ; i < vertices.length ; i++) { distances[vertices[i]] = 0 ; predecessors[vertices[i]] = null ; } while (!queue.isEmpty ()) { const u = queue.dequeue (); const neighbors = adjList.get (u); color[u] = Colors .GREY ; for (let i = 0 ; i < neighbors.length ; i++) { const w = neighbors[i]; if (color[w] === Colors .WHITE ) { color[w] = Colors .GREY ; distances[w] = distances[u] + 1 ; predecessors[w] = u; queue.enqueue (w); } } color[u] = Colors .BLACK ; } return { distances, predecessors }; };const shortestPathA = BFS (graph, myVertices[0 ]);console .log (shortestPathA.distances );console .log (shortestPathA.predecessors );const fromVertex = myVertices[0 ]; for (let i = 1 ; i < myVertices.length ; i++) { const toVertex = myVertices[i]; const path = new Stack (); for ( let v = toVertex; v !== fromVertex; v = shortestPathA.predecessors [v] ) { path.push (v); } path.push (fromVertex); let s = path.pop (); while (!path.isEmpty ()) { s += ' - ' + path.pop (); } console .log (s); }