▶ 书中第六章部分程序,包括在加上自己补充的代码,包括二分图最大匹配(最小顶点覆盖)的交替路径算法和 HopcroftKarp 算法
● 二分图最大匹配(最小顶点覆盖)的交替路径算法
1 package package01; 2 3 import edu.princeton.cs.algs4.StdOut; 4 import edu.princeton.cs.algs4.BipartiteX; 5 import edu.princeton.cs.algs4.Graph; 6 import edu.princeton.cs.algs4.GraphGenerator; 7 import edu.princeton.cs.algs4.Queue; 8 9 public class class01 10 { 11 private final int V; 12 private BipartiteX bipartition; 13 private int cardinality; // 匹配集的顶点数 14 private int[] mate; // 每个顶点的配对顶点,-1 表示未配对 15 private boolean[] inMinVertexCover; // 顶点是否处于最小覆盖中 16 private boolean[] marked; // 标记已经搜索过的顶点 17 private int[] edgeTo; // 搜索序列中的父节点标号 18 19 public class01(Graph G) 20 { 21 V = G.V(); 22 mate = new int[V]; 23 for (int v = 0; v < V; v++) 24 mate[v] = -1; 25 for (; hasAugmentingPath(G);) 26 { 27 int t = -1; 28 for (int v = 0; v < G.V(); v++) // 寻找增广路径的一个端点,它不在 mate 表中,但在搜索序列中 29 { 30 if (!isMatched(v) && edgeTo[v] != -1) 31 { 32 t = v; 33 break; 34 } 35 } 36 for (int v = t; v != -1; v = edgeTo[edgeTo[v]]) // 增广路经中每两个相邻顶点做成匹配 37 { 38 int w = edgeTo[v]; 39 mate[v] = w; 40 mate[w] = v; 41 } 42 cardinality++; // 全部调整完成,匹配集中顶点数量增加 1 43 } 44 inMinVertexCover = new boolean[V]; // 更新 inMinVertexCover[],加入最小覆盖的条件是未匹配的黑点和已匹配的红点 45 for (int v = 0; v < V; v++) 46 { 47 if (bipartition.color(v) && !marked[v] || !bipartition.color(v) && marked[v]) 48 inMinVertexCover[v] = true; 49 } 50 assert certifySolution(G); 51 } 52 53 private boolean hasAugmentingPath(Graph G) // 是否存在可调整路径,算法是寻找图中最短增广路经,注意函数对 mate[] 只读 54 // 交替路径:沿着交替路径前进,每条边依次属于、不属于匹配集合 55 // 增广路经:起点和终点都属于未匹配集的交替路径,说明可以通过对换路径上所有边(匹配 <-> 未匹配)来增加匹配集的顶点数 56 { 57 marked = new boolean[V]; 58 edgeTo = new int[V]; 59 for (int v = 0; v < V; v++) 60 edgeTo[v] = -1; 61 Queue<Integer> queue = new Queue<Integer>(); 62 for (int v = 0; v < V; v++) 63 { 64 if (bipartition.color(v) && !isMatched(v)) // 所有未配对的黑色点(true)加入搜索队列,作为初始点 65 { 66 queue.enqueue(v); 67 marked[v] = true; 68 } 69 } 70 for (; !queue.isEmpty();) 71 { 72 int v = queue.dequeue(); 73 for (int w : G.adj(v)) 74 { 75 if (isResidualGraphEdge(v, w) && !marked[w])// w 是一个新顶点,v - w 是未配对黑-红边且或是已配对红-黑边 76 { 77 edgeTo[w] = v; // 搜索序列中,把 w 当做 v 的后继 78 marked[w] = true; 79 if (!isMatched(w)) // w 是未经配对的顶点,说明找到了增广路经,否则 w 也加入搜索队列 80 return true; 81 queue.enqueue(w); 82 } 83 } 84 } 85 return false; 86 } 87 88 private boolean isResidualGraphEdge(int v, int w) // 要么 v 黑且 v - w 未配对,要么 v 红且 v - w 已配对 89 { 90 return (mate[v] != w) && bipartition.color(v) || (mate[v] == w) && !bipartition.color(v); 91 } 92 93 public int mate(int v) 94 { 95 return mate[v]; 96 } 97 98 public boolean isMatched(int v) // mate[v] >= 0 返回 1,mate[v] == -1 返回 0 99 { 100 return mate[v] != -1; 101 } 102 103 public int size() 104 { 105 return cardinality; 106 } 107 108 public boolean isPerfect() // 是否为完美匹配 109 { 110 return cardinality * 2 == V; 111 } 112 113 public boolean inMinVertexCover(int v) // 顶点是否在最小覆盖中 114 { 115 return inMinVertexCover[v]; 116 } 117 118 private boolean certifySolution(Graph G) // 检查结果正确性 119 { 120 for (int v = 0; v < V; v++) // 检查 mate[] 对称性 121 { 122 if (mate(v) == -1) 123 continue; 124 if (mate(mate(v)) != v) 125 return false; 126 } 127 int matchedVertices = 0; // 检查 cardinality 与 mate[] 一致性 128 for (int v = 0; v < V; v++) 129 { 130 if (mate(v) != -1) 131 matchedVertices++; 132 } 133 if (2 * size() != matchedVertices) 134 return false; 135 int sizeOfMinVertexCover = 0; // 检查 cardinality 与 inMinVertexCover[] 一致性 136 for (int v = 0; v < V; v++) 137 { 138 if (inMinVertexCover(v)) 139 sizeOfMinVertexCover++; 140 } 141 if (size() != sizeOfMinVertexCover) 142 return false; 143 boolean[] isMatched = new boolean[V]; // 检查 mate[] 唯一性 144 for (int v = 0; v < V; v++) 145 { 146 int w = mate[v]; 147 if (w == -1) 148 continue; 149 if (v == w) 150 return false; 151 if (v >= w) 152 continue; 153 if (isMatched[v] || isMatched[w]) return false; 154 isMatched[v] = true; 155 isMatched[w] = true; 156 } 157 for (int v = 0; v < V; v++) // 检查匹配集中的每个顶点至少有一条远端也属于匹配集的边 158 { 159 if (mate(v) == -1) 160 continue; 161 boolean isEdge = false; 162 for (int w : G.adj(v)) 163 { 164 if (mate(v) == w) 165 isEdge = true; 166 } 167 if (!isEdge) 168 return false; 169 } 170 for (int v = 0; v < V; v++) // 检查 inMinVertexCover[] 是一个覆盖 171 { 172 for (int w : G.adj(v)) 173 { 174 if (!inMinVertexCover(v) && !inMinVertexCover(w)) 175 return false; 176 } 177 } 178 return true; 179 } 180 181 public static void main(String[] args) 182 { 183 int V1 = Integer.parseInt(args[0]); 184 int V2 = Integer.parseInt(args[1]); 185 int E = Integer.parseInt(args[2]); 186 Graph G = GraphGenerator.bipartite(V1, V2, E); 187 if (G.V() < 1000) 188 StdOut.println(G); 189 190 class01 matching = new class01(G); 191 192 StdOut.printf("Number of edges in max matching = %d ", matching.size()); 193 StdOut.printf("Number of vertices in min vertex cover = %d ", matching.size()); 194 StdOut.printf("Graph has a perfect matching = %b ", matching.isPerfect()); 195 StdOut.println(); 196 197 if (G.V() >= 1000) 198 return; 199 StdOut.print("Max matching: "); 200 for (int v = 0; v < G.V(); v++) 201 { 202 int w = matching.mate(v); 203 if (matching.isMatched(v) && v < w) 204 StdOut.print(v + "-" + w + " "); 205 } 206 StdOut.print(" Min vertex cover: "); 207 for (int v = 0; v < G.V(); v++) 208 { 209 if (matching.inMinVertexCover(v)) 210 StdOut.print(v + " "); 211 } 212 StdOut.println(); 213 } 214 }
● 二分图最大匹配(最小顶点覆盖)的 HopcroftKarp 算法,仅注释与普通交替路径法不同的地方
1 package package01; 2 3 import java.util.Iterator; 4 import edu.princeton.cs.algs4.StdOut; 5 import edu.princeton.cs.algs4.BipartiteX; 6 import edu.princeton.cs.algs4.Stack; 7 import edu.princeton.cs.algs4.Graph; 8 import edu.princeton.cs.algs4.GraphGenerator; 9 import edu.princeton.cs.algs4.Queue; 10 11 public class class01 12 { 13 private final int V; 14 private BipartiteX bipartition; 15 private int cardinality; 16 private int[] mate; 17 private boolean[] inMinVertexCover; 18 private boolean[] marked; 19 private int[] distTo; // 搜索序列中抵达每个顶点的最小路径长度 20 21 public class01(Graph G) 22 { 23 V = G.V(); 24 mate = new int[V]; 25 for (int v = 0; v < V; v++) 26 mate[v] = -1; 27 for (; hasAugmentingPath(G);) 28 { 29 Iterator<Integer>[] adj = (Iterator<Integer>[]) new Iterator[G.V()]; // 邻接表迭代器列表 30 for (int v = 0; v < G.V(); v++) 31 adj[v] = G.adj(v).iterator(); 32 for (int s = 0; s < V; s++) 33 { 34 if (isMatched(s) || !bipartition.color(s)) // 只取未配对的黑色(true)顶点 35 continue; 36 Stack<Integer> path = new Stack<Integer>(); // 使用非递归的深度优先遍历寻找关于顶点 s 的交替路径 37 for (path.push(s); !path.isEmpty();) 38 { 39 int v = path.peek(); 40 if (!adj[v].hasNext()) // 栈顶顶点没有出边,跳过 41 { 42 path.pop(); 43 continue; 44 } 45 int w = adj[v].next(); 46 if (!isLevelGraphEdge(v, w)) // 若 v -w 不是搜索队列生成的边,跳过 47 continue; 48 path.push(w); 49 if (!isMatched(w)) // w 是新点,它不在 mate 表中,但在搜索序列中 50 { 51 for (; !path.isEmpty();) // 增广路经中每两个相邻顶点做成匹配 52 { 53 int x = path.pop(), y = path.pop(); 54 mate[x] = y; 55 mate[y] = x; 56 } 57 cardinality++; 58 } 59 } 60 } 61 } 62 inMinVertexCover = new boolean[V]; 63 for (int v = 0; v < V; v++) 64 { 65 if (bipartition.color(v) && !marked[v] || !bipartition.color(v) && marked[v]) 66 inMinVertexCover[v] = true; 67 } 68 } 69 70 private boolean hasAugmentingPath(Graph G) 71 { 72 marked = new boolean[V]; 73 distTo = new int[V]; 74 for (int v = 0; v < V; v++) 75 distTo[v] = Integer.MAX_VALUE; 76 Queue<Integer> queue = new Queue<Integer>(); // 从未配对的顶点开始广度优先搜索 77 for (int v = 0; v < V; v++) 78 { 79 if (bipartition.color(v) && !isMatched(v)) // 所有未配对的黑色点(true)加入搜索队列,作为初始点,初始点距离为 0 80 { 81 queue.enqueue(v); 82 marked[v] = true; 83 distTo[v] = 0; 84 } 85 } 86 boolean hasAugmentingPath = false; 87 for (; !queue.isEmpty();) // 要运行直到所有顶点都被遍历过才结束 88 { 89 int v = queue.dequeue(); 90 for (int w : G.adj(v)) 91 { 92 if (isResidualGraphEdge(v, w) && !marked[w]) 93 { 94 distTo[w] = distTo[v] + 1; // 标记顶点距离为已知顶点加 1 而不标记父节点编号 95 marked[w] = true; 96 if (!isMatched(w)) 97 hasAugmentingPath = true; // 仅标记发现了交替路径而不返回 98 if (!hasAugmentingPath) 99 queue.enqueue(w); 100 } 101 } 102 } 103 return hasAugmentingPath; 104 } 105 106 private boolean isResidualGraphEdge(int v, int w) 107 { 108 return (mate[v] != w) && bipartition.color(v) || (mate[v] == w) && !bipartition.color(v); 109 } 110 111 private boolean isLevelGraphEdge(int v, int w) // v - w 是搜索队列生成的边 112 { 113 return (distTo[w] == distTo[v] + 1) && isResidualGraphEdge(v, w); 114 } 115 116 public int mate(int v) 117 { 118 return mate[v]; 119 } 120 121 public boolean isMatched(int v) 122 { 123 return mate[v] != -1; 124 } 125 126 public int size() 127 { 128 return cardinality; 129 } 130 131 public boolean isPerfect() 132 { 133 return cardinality * 2 == V; 134 } 135 136 public boolean inMinVertexCover(int v) 137 { 138 return inMinVertexCover[v]; 139 } 140 141 private static String toString(Iterable<Integer> path) 142 { 143 StringBuilder sb = new StringBuilder(); 144 for (int v : path) 145 sb.append(v + "-"); 146 String s = sb.toString(); 147 s = s.substring(0, s.lastIndexOf('-')); 148 return s; 149 } 150 151 public static void main(String[] args) 152 { 153 int V1 = Integer.parseInt(args[0]); 154 int V2 = Integer.parseInt(args[1]); 155 int E = Integer.parseInt(args[2]); 156 Graph G = GraphGenerator.bipartite(V1, V2, E); 157 if (G.V() < 1000) 158 StdOut.println(G); 159 160 class01 matching = new class01(G); 161 162 StdOut.printf("Number of edges in max matching = %d ", matching.size()); 163 StdOut.printf("Number of vertices in min vertex cover = %d ", matching.size()); 164 StdOut.printf("Graph has a perfect matching = %b ", matching.isPerfect()); 165 StdOut.println(); 166 167 if (G.V() >= 1000) 168 return; 169 StdOut.print("Max matching: "); 170 for (int v = 0; v < G.V(); v++) 171 { 172 int w = matching.mate(v); 173 if (matching.isMatched(v) && v < w) 174 StdOut.print(v + "-" + w + " "); 175 } 176 StdOut.print(" Min vertex cover: "); 177 for (int v = 0; v < G.V(); v++) 178 { 179 if (matching.inMinVertexCover(v)) 180 StdOut.print(v + " "); 181 } 182 StdOut.println(); 183 } 184 }