4.1.1 走迷宫:Tremaux搜索

  • 选择一条没有标记过(marked)的通道,在走过的路上铺一条绳子
  • 标记所有第一次走过的路口和通道
  • 当来到一个已经被标记过的路口,则回退到上个路口
  • 回退到的路口无路可走时,继续回退

4.1.2 深度优先搜索DFS

  • 描述:DFS是一种搜索连通图的递归算法,只需一个递归就可以遍历所有结点,当到达某个结点时
    • 将它标记为已访问
    • 继续递归访问它没有标记过的邻居结点

实现

  1. public class DepthFirstSearch {
  2. private boolean[] marked;//记录已标记结点
  3. private int count;//连通结点个数
  4. public DepthFirstSearch(Graph G, int s){
  5. marked = new boolean[G.V()];
  6. dfs(G, s);
  7. }
  8. private void dfs(Graph G, int v){
  9. if (marked[v]) return;//若已被标记,则返回上一路口
  10. marked[v] = true;
  11. count++;
  12. for (int w: G.adj(v)){
  13. dfs(G, w);
  14. }
  15. }
  16. public boolean marked(int w){
  17. return marked[w];
  18. }
  19. public int count(){
  20. return count;
  21. }
  22. }

算法分析

  • 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

实现

  1. public class Paths {
  2. private boolean[] marked;//该顶点上是否调用dfs()
  3. private int[] edgeTo;//起点到上一顶点路径上的最后一个顶点
  4. private final int s;//起点
  5. public Paths(Graph G, int s){
  6. marked = new boolean[G.V()];
  7. edgeTo = new int[G.V()];
  8. this.s = s;
  9. dfs(G, s);
  10. }
  11. //深度优先遍历(递归/隐式栈)
  12. private void dfs(Graph G, int v){
  13. marked[v] = true;
  14. for (int w: G.adj(v)){
  15. if (!marked[w]){
  16. edgeTo[w] = v;//到w的上一个结点为v
  17. dfs(G, w);
  18. }
  19. }
  20. }
  21. //是否有入度
  22. private boolean hasPathTo(int v){ return marked[v]; }
  23. //返回s到v的路径(Stack形式)
  24. public Iterable<Integer> pathTo(int v){
  25. if (!hasPathTo(v)) return null;//没有调用过dfs(),则v一定是孤立结点,直接返回null
  26. Stack<Integer> path = new Stack<>();
  27. for (int i = v; i != s; i = edgeTo[i])
  28. path.push(i);//上一个结点压入栈中
  29. path.push(s);//压入起点
  30. return path;
  31. }
  32. }
  • 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找出连通分量

  1. import edu.princeton.cs.algs4.In;
  2. import edu.princeton.cs.algs4.StdOut;
  3. public class CC {
  4. private boolean[] marked;
  5. private int[] id;//已知边最后顶点
  6. private int count;//连通分量数
  7. public CC(Graph G) {
  8. marked = new boolean[G.V()];
  9. id = new int[G.V()];
  10. for(int i = 0; i < G.V(); i++){
  11. if (!marked[i]){
  12. dfs(G,i);//深度搜索一个节点后,分量加一
  13. count++;
  14. }
  15. }
  16. }
  17. private void dfs(Graph G, int v){
  18. marked[v] = true;
  19. id[v] = count;
  20. for (int w:G.adj(v))
  21. if (!marked[w])
  22. dfs(G,w);
  23. }
  24. public boolean connected(int v, int w) { return id[v] == id[w];}
  25. public int count(){ return count;}
  26. public int id(int v){ return id[v];}
  27. public static void main(String[] args){
  28. Graph G = new Graph(new In(args[0]));
  29. CC cc = new CC(G);
  30. int M = cc.count();
  31. StdOut.println(M + " components");
  32. Bag<Integer>[] components;//Bag对象数组(邻接表)
  33. components = (Bag<Integer>[]) new Bag[M];
  34. for (int i = 0; i < M; i++)
  35. components[i] = new Bag();//初始化分量数组
  36. for (int v = 0; v < G.V(); v++)
  37. components[cc.id(v)].add(v);//添加元素
  38. for (int i = 0; i < M; i++){
  39. for (int v: components[i])
  40. StdOut.print(v + " ");
  41. StdOut.println();
  42. }
  43. }
  44. }

算法分析

  • DFS的与处理时间和空间与V+E成正比且可以在常数时间内处理关于图的连通性查询:每个邻接表元素只会被检查一次

对比union-find

  • DFS可以保证所需时间是常数,而UF不行,但实际中UF更快,后者不需要完整构造一幅图,可以在任何时候检查两点是否连通,但前者必须对图进行预处理