4.1.1 走迷宫:Tremaux搜索
- 选择一条没有标记过(marked)的通道,在走过的路上铺一条绳子
- 标记所有第一次走过的路口和通道
- 当来到一个已经被标记过的路口,则回退到上个路口
- 回退到的路口无路可走时,继续回退
4.1.2 深度优先搜索DFS
- 描述:DFS是一种搜索连通图的递归算法,只需一个递归就可以遍历所有结点,当到达某个结点时
- 将它标记为已访问
- 继续递归访问它没有标记过的邻居结点
实现
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){if (marked[v]) return;//若已被标记,则返回上一路口marked[v] = true;count++;for (int w: G.adj(v)){dfs(G, w);}}public boolean marked(int w){return marked[w];}public int count(){return count;}}
算法分析
- DFS标记与顶点s相连通所有顶点的时间和∑deg(v)成正比:DFS中每条边都会被访问两次,且第二次总会发现已被访问过
4.1.4 寻找路径
- 问题:给定一幅图和一个起点s,问”s到给定的目的结点v之间是否有一条路径,如果有请找出”,此类问题即为寻找路径
路径API:public class Paths
| 返回类型 | 方法 | 描述 |
|---|---|---|
| Paths(Graph G, int s) | 在G中找出所有起点s的路径 | |
| boolean | hasPathTo(int v) | 是否存在s到v的路径 |
| Iterable | pathTo(int v) | s到v的路径,不存在返回null |
实现
public class Paths {private boolean[] marked;//该顶点上是否调用dfs()private int[] edgeTo;//起点到上一顶点路径上的最后一个顶点private final int s;//起点public Paths(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;//到w的上一个结点为vdfs(G, w);}}}//是否有入度private boolean hasPathTo(int v){ return marked[v]; }//返回s到v的路径(Stack形式)public Iterable<Integer> pathTo(int v){if (!hasPathTo(v)) return null;//没有调用过dfs(),则v一定是孤立结点,直接返回nullStack<Integer> path = new Stack<>();for (int i = v; i != s; i = edgeTo[i])path.push(i);//上一个结点压入栈中path.push(s);//压入起点return path;}}
- edgeTo[w] = v表示到w的上一个结点为v,即起点s到v的最后一条已知边为v-w
edgeTo[2] = 0;
edgeTo[1] = 2;
edgeTo[3] = 2; edgeTo[5] = 3;
edgeTo[4] = 3;
- 最终路径Path由Stack/Bag实现
算法分析
- DFS标记起连通的所有顶点耗时和∑deg(v)成正比
- 使用DFS得到的从给定点s到任意点v的path所需时间和路径长度成正比
算法4.3 DFS找出连通分量
import edu.princeton.cs.algs4.In;import edu.princeton.cs.algs4.StdOut;public class CC {private boolean[] marked;private int[] id;//已知边最后顶点private int count;//连通分量数public CC(Graph G) {marked = new boolean[G.V()];id = new int[G.V()];for(int i = 0; i < G.V(); i++){if (!marked[i]){dfs(G,i);//深度搜索一个节点后,分量加一count++;}}}private void dfs(Graph G, int v){marked[v] = true;id[v] = count;for (int w:G.adj(v))if (!marked[w])dfs(G,w);}public boolean connected(int v, int w) { return id[v] == id[w];}public int count(){ return count;}public int id(int v){ return id[v];}public static void main(String[] args){Graph G = new Graph(new In(args[0]));CC cc = new CC(G);int M = cc.count();StdOut.println(M + " components");Bag<Integer>[] components;//Bag对象数组(邻接表)components = (Bag<Integer>[]) new Bag[M];for (int i = 0; i < M; i++)components[i] = new Bag();//初始化分量数组for (int v = 0; v < G.V(); v++)components[cc.id(v)].add(v);//添加元素for (int i = 0; i < M; i++){for (int v: components[i])StdOut.print(v + " ");StdOut.println();}}}
算法分析
- DFS的与处理时间和空间与V+E成正比且可以在常数时间内处理关于图的连通性查询:每个邻接表元素只会被检查一次
对比union-find
- DFS可以保证所需时间是常数,而UF不行,但实际中UF更快,后者不需要完整构造一幅图,可以在任何时候检查两点是否连通,但前者必须对图进行预处理
