▶ 书中第四章部分程序,包括在加上自己补充的代码,包括无向图连通分量,Kosaraju - Sharir 算法、Tarjan 算法、Gabow 算法计算有向图的强连通分量
● 无向图连通分量
1 package package01; 2 3 import edu.princeton.cs.algs4.In; 4 import edu.princeton.cs.algs4.StdOut; 5 import edu.princeton.cs.algs4.Graph; 6 import edu.princeton.cs.algs4.Queue; 7 import edu.princeton.cs.algs4.EdgeWeightedGraph; 8 import edu.princeton.cs.algs4.Edge; 9 10 public class class01 11 { 12 private boolean[] marked; 13 private int[] id; // 连通分量的标号 14 private int[] size; // 连通分量顶点数 15 private int count; // 连通分量数 16 17 public class01(Graph G) 18 { 19 marked = new boolean[G.V()]; 20 id = new int[G.V()]; 21 size = new int[G.V()]; 22 for (int v = 0; v < G.V(); v++) 23 { 24 if (!marked[v]) 25 { 26 dfs(G, v); 27 count++; 28 } 29 } 30 } 31 32 public class01(EdgeWeightedGraph G) // 有边权的图,算法相同,数据类型不同 33 { 34 marked = new boolean[G.V()]; 35 id = new int[G.V()]; 36 size = new int[G.V()]; 37 for (int v = 0; v < G.V(); v++) 38 { 39 if (!marked[v]) 40 { 41 dfs(G, v); 42 count++; 43 } 44 } 45 } 46 47 private void dfs(Graph G, int v) 48 { 49 marked[v] = true; 50 id[v] = count; // 深度优先搜索时,顶点分类,连通分量尺寸更新 51 size[count]++; 52 for (int w : G.adj(v)) 53 { 54 if (!marked[w]) 55 dfs(G, w); 56 } 57 } 58 59 private void dfs(EdgeWeightedGraph G, int v) 60 { 61 marked[v] = true; 62 id[v] = count; 63 size[count]++; 64 for (Edge e : G.adj(v)) 65 { 66 int w = e.other(v); 67 if (!marked[w]) 68 dfs(G, w); 69 } 70 } 71 72 public int id(int v) 73 { 74 return id[v]; 75 } 76 77 public int size(int v) 78 { 79 return size[id[v]]; 80 } 81 82 public int count() 83 { 84 return count; 85 } 86 87 public boolean connected(int v, int w) 88 { 89 return id[v] == id[w]; 90 } 91 92 public static void main(String[] args) 93 { 94 In in = new In(args[0]); 95 Graph G = new Graph(in); 96 class01 cc = new class01(G); 97 int m = cc.count(); 98 Queue<Integer>[] components = (Queue<Integer>[]) new Queue[m]; // 每个连通分量放入一个队列 99 for (int i = 0; i < m; i++) 100 components[i] = new Queue<Integer>(); 101 for (int v = 0; v < G.V(); v++) 102 components[cc.id(v)].enqueue(v); 103 104 StdOut.println(m + " components"); 105 for (int i = 0; i < m; i++) 106 { 107 for (int v : components[i]) 108 StdOut.print(v + " "); 109 StdOut.println(); 110 } 111 } 112 }
● Kosaraju - Sharir 算法计算有向图的强连通分量
1 package package01; 2 3 import edu.princeton.cs.algs4.In; 4 import edu.princeton.cs.algs4.StdOut; 5 import edu.princeton.cs.algs4.Digraph; 6 import edu.princeton.cs.algs4.Queue; 7 import edu.princeton.cs.algs4.DepthFirstOrder; 8 9 public class class01 10 { 11 private boolean[] marked; 12 private int[] id; 13 private int[] size; 14 private int count; 15 16 public class01(Digraph G) 17 { 18 DepthFirstOrder dfs = new DepthFirstOrder(G.reverse()); // 对 G 的逆图进行深度优先搜索 19 marked = new boolean[G.V()]; 20 id = new int[G.V()]; 21 size = new int[G.V()]; 22 for (int v : dfs.reversePost()) // 使用 G 逆图 dfs 序对 G 进行深度优先搜索 23 { 24 if (!marked[v]) 25 { 26 dfs(G, v); 27 count++; 28 } 29 } 30 } 31 32 private void dfs(Digraph G, int v) 33 { 34 marked[v] = true; 35 id[v] = count; 36 size[count]++; 37 for (int w : G.adj(v)) 38 { 39 if (!marked[w]) 40 dfs(G, w); 41 } 42 } 43 44 public int id(int v) 45 { 46 return id[v]; 47 } 48 49 public int size(int v) 50 { 51 return size[id[v]]; 52 } 53 54 public int count() 55 { 56 return count; 57 } 58 59 public boolean strongConnected(int v, int w) 60 { 61 return id[v] == id[w]; 62 } 63 64 public static void main(String[] args) 65 { 66 In in = new In(args[0]); 67 Digraph G = new Digraph(in); 68 class01 scc = new class01(G); 69 int m = scc.count(); 70 Queue<Integer>[] components = (Queue<Integer>[]) new Queue[m]; 71 for (int i = 0; i < m; i++) 72 components[i] = new Queue<Integer>(); 73 for (int v = 0; v < G.V(); v++) 74 components[scc.id(v)].enqueue(v); 75 76 StdOut.println(m + " components"); 77 for (int i = 0; i < m; i++) 78 { 79 for (int v : components[i]) 80 StdOut.print(v + " "); 81 StdOut.println(); 82 } 83 } 84 }
● Tarjan 算法计算有向图的强连通分量
1 package package01; 2 3 import edu.princeton.cs.algs4.In; 4 import edu.princeton.cs.algs4.StdOut; 5 import edu.princeton.cs.algs4.Digraph; 6 import edu.princeton.cs.algs4.Queue; 7 import edu.princeton.cs.algs4.Stack; 8 9 public class class01 10 { 11 private boolean[] marked; 12 private int[] id; 13 private int[] size; 14 private int[] low; // 遍历序中顶点 v 的最小深度 15 private int pre; // 优先级(遍历顶点时不断维护) 16 private int count; 17 private Stack<Integer> stack; 18 19 public class01(Digraph G) 20 { 21 marked = new boolean[G.V()]; 22 id = new int[G.V()]; 23 size = new int[G.V()]; 24 low = new int[G.V()]; 25 stack = new Stack<Integer>(); 26 for (int v = 0; v < G.V(); v++) // 正常顺序深度优先遍历图 27 { 28 if (!marked[v]) 29 dfs(G, v); 30 } 31 } 32 33 private void dfs(Digraph G, int v) 34 { 35 marked[v] = true; 36 low[v] = pre++; // 更新递归深度 37 int min = low[v]; // 记录 v 所在的的递归深度 38 stack.push(v); 39 for (int w : G.adj(v)) 40 { 41 if (!marked[w]) 42 dfs(G, w); 43 if (low[w] < min) // 寻找 v 邻居的最小深度,low[w] < min 说明找到了后向边(v->w 关于栈中的元素构成有向环) 44 min = low[w]; 45 } 46 if (min < low[v]) // 改写 v 的最小深度,终止递归(沿着栈一路改写回去,直到栈中首次出现有向环的元素为止(再往前的顶点满足 low[x] < min),进入吐栈环节 47 { 48 low[v] = min; 49 return; 50 } 51 int sizeTemp = 0; 52 for (int w = -1; w != v; sizeTemp++) // 从栈顶吐到 v 为止,都是一个强连通分量,这里 v 是有向环首元再往前一格的顶点 53 { 54 w = stack.pop(); 55 id[w] = count; // 标记吐出的强连通分量 56 low[w] = G.V(); // 将已经记录了的连通分量的深度改为图的顶点数(递归可能的最大深度) 57 } 58 size[count] = sizeTemp; 59 count++; // 增加连通分量计数 60 } 61 62 public int id(int v) 63 { 64 return id[v]; 65 } 66 67 public int size(int v) 68 { 69 return size[id[v]]; 70 } 71 72 public int count() 73 { 74 return count; 75 } 76 77 public boolean strongConnected(int v, int w) 78 { 79 return id[v] == id[w]; 80 } 81 82 public static void main(String[] args) 83 { 84 In in = new In(args[0]); 85 Digraph G = new Digraph(in); 86 class01 scc = new class01(G); 87 int m = scc.count(); 88 Queue<Integer>[] components = (Queue<Integer>[]) new Queue[m]; 89 for (int i = 0; i < m; i++) 90 components[i] = new Queue<Integer>(); 91 for (int v = 0; v < G.V(); v++) 92 components[scc.id(v)].enqueue(v); 93 94 StdOut.println(m + " components"); 95 for (int i = 0; i < m; i++) 96 { 97 for (int v : components[i]) 98 StdOut.print(v + " "); 99 StdOut.println(); 100 } 101 } 102 }
● Gabow 算法计算有向图的强连通分量
1 package package01; 2 3 import edu.princeton.cs.algs4.In; 4 import edu.princeton.cs.algs4.StdOut; 5 import edu.princeton.cs.algs4.Digraph; 6 import edu.princeton.cs.algs4.Queue; 7 import edu.princeton.cs.algs4.Stack; 8 9 public class class01 10 { 11 private boolean[] marked; 12 private int[] id; 13 private int[] size; 14 private int[] preOrder; // 记录每个顶点的遍历深度 15 private int pre; 16 private int count; 17 private Stack<Integer> stack1; 18 private Stack<Integer> stack2; // 用栈来代替 Tarjan 算法中的 low 数组 19 20 public class01(Digraph G) 21 { 22 marked = new boolean[G.V()]; 23 id = new int[G.V()]; 24 size = new int[G.V()]; 25 preOrder = new int[G.V()]; 26 stack1 = new Stack<Integer>(); 27 stack2 = new Stack<Integer>(); 28 for (int v = 0; v < G.V(); v++) 29 id[v] = -1; 30 for (int v = 0; v < G.V(); v++) 31 { 32 if (!marked[v]) 33 dfs(G, v); 34 } 35 } 36 37 private void dfs(Digraph G, int v) 38 { 39 marked[v] = true; 40 preOrder[v] = pre++; // 更新递归深度,一旦写入就不改变了 41 stack1.push(v); // 同时压两个栈 42 stack2.push(v); 43 for (int w : G.adj(v)) 44 { 45 if (!marked[w]) 46 dfs(G, w); 47 else if (id[w] == -1) // 已经遍历过 w,且 w 不属于任何连通分量 48 for (; preOrder[stack2.peek()] > preOrder[w]; stack2.pop()); // 把 stack2 中深度大于 w 的顶点全部吐掉(直到栈顶等于有向环的首个元素为止) 49 } 50 if (stack2.peek() == v) // 该式在递归回退到栈中首次出现有向环元素的那层时成立,此时 stack2 顶为有向环首元,stack1 顶为有向环末元 51 { // 注意此时 stack2 顶层下(stack1 和 stack2 共有且相等)可能还有其他元素,是不属于非有向环部分的遍历路径 52 stack2.pop(); // stack2 退到首元的上一个元 53 int sizeTemp = 0; 54 for (int w = -1; w != v; sizeTemp++) // 同样的方法从 stack1 中逐渐吐栈,计算连通分量元素 55 { 56 w = stack1.pop(); 57 id[w] = count; 58 } 59 size[count] = sizeTemp; 60 count++; 61 } 62 } 63 64 public int id(int v) 65 { 66 return id[v]; 67 } 68 69 public int size(int v) 70 { 71 return size[id[v]]; 72 } 73 74 public int count() 75 { 76 return count; 77 } 78 79 public boolean strongConnected(int v, int w) 80 { 81 return id[v] == id[w]; 82 } 83 84 public static void main(String[] args) 85 { 86 In in = new In(args[0]); 87 Digraph G = new Digraph(in); 88 class01 scc = new class01(G); 89 int m = scc.count(); 90 Queue<Integer>[] components = (Queue<Integer>[]) new Queue[m]; 91 for (int i = 0; i < m; i++) 92 components[i] = new Queue<Integer>(); 93 for (int v = 0; v < G.V(); v++) 94 components[scc.id(v)].enqueue(v); 95 96 StdOut.println(m + " components"); 97 for (int i = 0; i < m; i++) 98 { 99 for (int v : components[i]) 100 StdOut.print(v + " "); 101 StdOut.println(); 102 } 103 } 104 }