深度优先搜索得到的路径不仅取决于图的结构,还取决于图的表示和递归调用的性质。我们很自然地还经常对下面这些问题感兴趣。
单点最短路径:给定一幅图和一个起点s,回答“从s到给定目的顶点v是否存在一条路径?如果有,找出其中最短的那条(含边数最少)”等类似问题。
解决这个问题的经典方法叫做广度优先搜索。深度优先搜索在这个问题上没有什么作为,因为它遍历整个图的顺序和找出最短路径的目标没有任何关系。
在深度优先搜索中,我们用了一个可以下压的栈(这是由系统管理的,以支持递归搜索方法)。在广度优先搜索中,我们希望按照与起点的距离的顺序来遍历所有顶点,使用(FIFO,先进先出)队列来代替栈(LIFO,后进后出)即可。
在下面算法实现中,我们使用了一个队列来保存所有已经被标记过但其邻接表还未被检查过的顶点。先将起点加入队列,然后重复以下步骤直到队列为空:
- 取队列中的下一个顶点v并标记它;
- 将与v相邻的所有未被标记过的顶点加入队列。
代码
其中图用到了Graph:
package section4_1;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
public class BreadthFirstPaths {
private boolean[] marked;
private int[] edgeTo;
private final int s;
public BreadthFirstPaths(Graph G, int s) {
marked = new boolean[G.V()];
edgeTo = new int[G.V()];
this.s = s;
bfs(G,s);
}
private void bfs(Graph G, int s) {
Queue<Integer> queue = new LinkedList<>();
marked[s] = true;
((LinkedList<Integer>) queue).addLast(s);
while (!queue.isEmpty()) {
int v = ((LinkedList<Integer>) queue).pollFirst();
for (int w : G.adj(v)) {
if (!marked[w]) {
edgeTo[w] = v;
marked[w] = true;
((LinkedList<Integer>) queue).addLast(w);
}
}
}
}
public boolean hasPathTo(int v) {
return marked[v];
}
public Iterable<Integer> pathTo(int v) {
if (!hasPathTo(v)) return null;
List<Integer> list = new ArrayList<>();
for (int x = v;x != s;x = edgeTo[x]) {
list.add(0,x);
}
list.add(0,s);
return list;
}
public static void main(String[] args) {
int[][] data = {
{0,5},
{2,4},
{2,3},
{1,2},
{0,1},
{3,4},
{3,5},
{0,2}
};
int vn = 6;
int e = 8;
Graph graph = new Graph(vn,e,data);
int s = 0;
BreadthFirstPaths bfs = new BreadthFirstPaths(graph,s);
for (int v = 0;v < graph.V();v++) {
System.out.print(s + " to " + v + ": ");
if (bfs.hasPathTo(v)) {
for (int x : bfs.pathTo(v)) {
if (x == s) System.out.print(x);
else System.out.print("-"+x);
}
}
System.out.println();
}
}
}
对于从s可达的任意顶点v,广度优先搜索都能找到一条从s到v的最短路径(没有其他从s到v的路径所含的边比这条路径更少)。
广度优先搜索所需的时间在最坏情况下和V+E成正比。
深度优先搜索和广度优先搜索是我们首先学习的几种通用的图搜索的算法之一。这两个算法的不同之处仅在于从数据结构中获取下一个顶点的规则(对于广度优先搜索来说是最早加入的顶点,,对于深度优先搜索来说是最晚加入的顶点)。无论使用哪种规则,所有与起点连通的顶点和边都会被检查到。
深度优先搜索不断深入图中并在栈中保存了所有分叉的顶点;广度优先搜索则像扇面一般扫描图,用一个队列保存访问过的最前端的顶点。
深度优先搜索探索一幅图的方式是寻找离起点更远的顶点,只在碰到死胡同时才访问近处的顶点;广度优先搜索则会首先覆盖起点附近的顶点,只在临近的所有顶点都被访问了之后才向前进。
深度优先搜索的路径通常较长而且曲折,广度优先搜索的路径则短而直接。