本文兼参考自《算法导论》及《算法》。
以前一直不能够理解深度优先搜索和广度优先搜索,总是很怕去碰它们,但经过阅读上边提到的两本书,豁然开朗,马上就能理解得更进一步。
下文将会用到的一个无向图例子如下:
深度优先搜索
迷宫搜索
在《算法》这本书中,作者写了很好的一个故事。这个故事让我马上理解了深度优先搜索的思想。
如下图1-1所示,如何在这个迷宫中找到出路呢?方法见图1-2.
图1-1 等价的迷宫模型
探索迷宫而不迷路的一种古老办法(至少可以追溯到忒修斯和米诺陶的传说)叫做Tremaux搜索,如下图所示。要探索迷宫中的所有通道,我们需要:
1)选择一条没有标记过的通道,在你走过的路上铺一条绳子;
2)标记所有你第一次路过的路口和通道;
3)当来到一个标记过的路口时,用绳子回退到上个路口;
4)当回退到的路口已没有可走的通道时继续回退。
绳子可以保证你总能找到一条出路,标记则能保证你不会两次经过同一条通道或同一个路口。
图1-2 Tremaux探索
深度优先搜索
《算法》作者给出的Java代码如下:
public class DepthFirstSearch
{
private boolean[] marked;
private int count; public DepthFirstSearch(Graph G, int s)
{
marked = new boolean[G.V()];
dfs(G, s);
} private void dfs(Graph G, int v)
{
marked[v] = true;
count++;
for(int w : G.adj(v))
{
if(!marked[w])
{
dfs(G, w);
}
}
} public boolean marked(int w)
{
return marked[w];
} public int count()
{
return count;
}
}
DFS.java
具体例子如下图1-3:
图1-3 使用深度优先探索的轨迹,寻找所有和顶点0连通的顶点
寻找路径
《算法》作者给出的Java代码如下:
public class DepthFirstPaths
{
private boolean[] marked; // Has dfs() been called for this vertex?
private int[] edgeTo; // last vertex on known path to this vertex
private final int s; // source
public DepthFirstPaths(Graph G, int s)
{
marked = new boolean[G.V()];
edgeTo = new int[G.V()];
this.s = s;
dfs(G, s);
} private void dfs(Graph G, int v)
{
marked[v] = true;
for (int w : G.adj(v))
if (!marked[w])
{
edgeTo[w] = v;
dfs(G, w);
}
} public boolean hasPathTo(int v)
{
return marked[v];
} public Iterable<Integer> pathTo(int v)
{
if (!hasPathTo(v)) return null;
Stack<Integer> path = new Stack<Integer>();
for (int x = v; x != s; x = edgeTo[x])
path.push(x);
path.push(s);
return path;
}
}
DFS.java
图1-4 pathTo(5)的计算轨迹
图1-5 使用深度优先搜索的轨迹,寻找所有起点为0的路径
广度优先搜索
迷宫搜索
深度优先搜索就好像是一个人在走迷宫,广度优先搜索则好像是一组人在一起朝各个方向走这座迷宫,每个人都有自己的绳子。当出现新的叉路时,可以假设一个探索者可以分裂为更多的人来搜索它们。当两个探索者相遇时,会合二为一(并继续使用先到达者的绳子)。如下图2-1:
图2-1 广度优先的迷宫搜索
广度优先搜索
《算法》作者给出的使用广度优先搜索查找图中的路径的Java代码如下:
public class BreadthFirstPaths
{
private boolean[] marked; // Is a shortest path to this vertex known?
private int[] edgeTo; // last vertex on known path to this vertex
private final int s; // source 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 Queue<Integer>();
marked[s] = true; // Mark the source
queue.enqueue(s); // and put it on the queue.
while (!q.isEmpty())
{
int v = queue.dequeue(); // Remove next vertex from the queue.
for (int w : G.adj(v))
if (!marked[w]) // For every unmarked adjacent vertex,
{
edgeTo[w] = v; // save last edge on a shortest path,
marked[w] = true; // mark it because path is known,
queue.enqueue(w); // and add it to the queue.
}
}
} public boolean hasPathTo(int v)
{
return marked[v];
} public Iterable<Integer> pathTo(int v)
// Same as for DFS.
}
BFS.java
一个例子如下:
图2-2 使用广度优先搜索寻找所有起点为0的路径的结果
图2-3 使用广度优先搜索的轨迹,寻找所有起点为0的路径
具体代码已经托管到Github.