广度优先搜索(BFS)
上一次已经提到,图的遍历一般有两种算法,即广度优先和深度优先。其中广度优先搜索算法会从指定的第一个顶点开始遍历图,先访问其所有的相邻点,就像一次访问图的一层。换句话说,就是先宽后深地访问顶点,如图1。
图1
从顶点v开始的广度优先搜索的步骤如下:
- 创建一个队列Q。
- 将v标注为被发现的(灰色) ,并将v入队列Q。
-
如果Q非空,则运行以下步骤:
- 将u从队列Q中取出
- 将u标注为被发现的(灰色)
- 将u所有未被访问过的邻节点(白色)加入队列
- 将u标注为已被探索的(黑色)
我们用三种状态来反映顶点的状态:
- 白色:表示该顶点还没有被访问。
- 灰色:表示该顶点被访问过,但并未被探索过。
- 黑色:表示该顶点被访问过且被完全探索过。
因此在算法开始执行时,需要将所有顶点置为白色;如下代码所示(本文的所有代码都是在图中实现的Graph类中添加的),其中vertices保存着图所有顶点的名字。
function Graph() {
var vertices = [];
var adjList = new Dictionary();
// 图类的其他代码省略...
var initializeColor = function(){
var color = [];
for (var i=0; i<vertices.length; i++){
color[vertices[i]] = 'white';
}
return color;
};
}
广度优先搜索算法的核心代码如下,其中队列Queue的实现参考基于JavaScript的数据结构 — 队列的实现
this.bfs = function(v, callback){
var color = initializeColor(), // 将所有顶点初始化为白色
queue = new Queue(); // 实例化队列
queue.enqueue(v); // 将起始顶点v加入队列
while (!queue.isEmpty()){ // 一直循环处理队列,直到队列为空
var u = queue.dequeue(), // 移除队列顶部的元素,并取得该顶点
neighbors = adjList.get(u); // 获取该顶点的相邻顶点
color[u] = 'grey'; // 将该顶点置为灰色,表明该顶点被访问过,但并未被探索过
for (var i=0; i<neighbors.length; i++){ // 循环处理该顶点的相邻顶点
var w = neighbors[i];
if (color[w] === 'white'){ // 如果该顶点没有被访问过
color[w] = 'grey'; // 将该顶点置为灰
queue.enqueue(w); // 将该顶点加入队列
}
}
color[u] = 'black'; // 该顶点访问已经被完全访问,将其置为黑
callback && callback(u); // 用回调函数处理该节点(如果有)
}
};
使用BFS寻找最短路径
由于广度优先算法是一层层往下遍历的,即先访问与起始顶点距离为1的点,再访问距离为2的点,以此类推。因此,给定一个图G和源顶点v, 要找出每一个顶点u与v之间的最短距离(以边的数量计算),可以在上述代码的基础上做一定修改(修改的位置用空行隔开):
// 获取路径信息
this.pathData = function(v){
var color = initializeColor(),
queue = new Queue(),
d = new Array(vertices.length).fill(0), // 用于保存起始顶点v到任意顶点u的距离
pred = new Array(vertices.length).fill(null); // 用于保存v到u的路径上u的上一级顶点(前溯点)
queue.enqueue(v);
while (!queue.isEmpty()){
var u = queue.dequeue(),
neighbors = adjList.get(u);
color[u] = 'grey';
for (i=0; i<neighbors.length; i++){
var w = neighbors[i];
if (color[w] === 'white'){
color[w] = 'grey';
d[w] = d[u] + 1; // w到u的距离差是1
pred[w] = u; // w的上一级顶点是u
queue.enqueue(w);
}
}
color[u] = 'black';
}
return { // 返回保存的数据
distances: d,
predecessors: pred
};
};
// 格式化输出路径信息
this.printPathData = function (pathData) {
var fromVertex = vertices[0]; // 获取起始点
for (var i=1; i<vertices.length; i++){
var toVertex = vertices[i], // 要到达的顶点
path = []; // 用于保存路径
// 从目标顶点一直回溯到起始顶点
for (var v=toVertex; v!== fromVertex; v= pathData.predecessors[v]) {
path.push(v); // 顶点添加进路径
}
path.push(fromVertex); // 将起始顶点添加进路径
var s = path.pop();
while (path.length > 0){
s += ' - ' + path.pop(); // 从路径数组倒序输出顶点
}
console.log(s);
}
}
通过执行Graph.printPathData(Graph.pathData())即可输出起始顶点到每一个顶点的最短路径。
其它最短路径算法
对于加权图的最短路径,广度优先算法可能并不合适。比如,Dijkstra’s算法可以解决单源最短路径问题。Bellman–Ford算法解决了边权值为负的单源最短路径问题。A*搜索算法解决了求仅一对顶点间的最短路径问题,它用经验法则来加速搜索过程。Floyd–Warshall算法解决了求所有顶点对间的最短路径这一问题。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。