《算法》第六章部分程序 part 6

▶ 书中第六章部分程序,包括在加上自己补充的代码,包括全局最小切分 Stoer-Wagner 算法,二分图最大匹配(最小顶点覆盖)的交替路径算法和 HopcroftKarp 算法

● 全局最小切分 Stoer-Wagner 算法

  1 package package01;
  2 
  3 import edu.princeton.cs.algs4.In;
  4 import edu.princeton.cs.algs4.StdOut;
  5 import edu.princeton.cs.algs4.EdgeWeightedGraph;
  6 import edu.princeton.cs.algs4.Edge;
  7 import edu.princeton.cs.algs4.UF;
  8 import edu.princeton.cs.algs4.IndexMaxPQ;
  9 
 10 public class class01
 11 {
 12     private static final double FLOATING_POINT_EPSILON = 1E-11;
 13     private double weight = Double.POSITIVE_INFINITY;           // 输出的最小割值
 14     private boolean[] cut;                                      // 顶点是否在割除集 T 中
 15     private int V;
 16 
 17     private class CutPhase                                      // 最小 s-t 割类(cut-of-the-phase)
 18     {
 19         private double weight;
 20         private int s;
 21         private int t;
 22 
 23         public CutPhase(double inputWeight, int inputS, int inputT)
 24         {
 25             weight = inputWeight;
 26             s = inputS;
 27             t = inputT;
 28         }
 29     }
 30 
 31     public class01(EdgeWeightedGraph G)
 32     {
 33         UF uf = new UF(G.V());                              // 用类 union–find 来表示顶点的合并情况
 34         boolean[] marked = new boolean[G.V()];              // 已合并的顶点集,初始化为空
 35         cut = new boolean[G.V()];                           // 割除集 T,初始化为空
 36         CutPhase cp = new CutPhase(0.0, 0, 0);              // 用于首次搜索的割,无意义
 37         for (int v = G.V(); --v > 0; marked[cp.t] = true)   // 遍历 V-1 次,每次标记被合并的顶点,以后不再遍历该点
 38         {
 39             cp = minCutPhase(G, marked, cp);                // 更新最小割
 40             if (cp.weight < weight)                         // 发现权值更小的割,更新全局割
 41             {
 42                 weight = cp.weight;
 43                 for (int j = 0; j < G.V(); j++)             // 在最新的图中,与 cp.t 相连的顶点都是 T 的元素
 44                     cut[j] = uf.connected(j, cp.t);
 45             }
 46             G = contractEdge(G, cp.s, cp.t);                // 顶点 t 合并到顶点 s,更新图
 47             uf.union(cp.s, cp.t);                           // 顶点 t 加入 T 中
 48         }
 49     }
 50 
 51     public double weight()
 52     {
 53         return weight;
 54     }
 55 
 56     public boolean cut(int v)
 57     {
 58         return cut[v];
 59     }
 60 
 61     private CutPhase minCutPhase(EdgeWeightedGraph G, boolean[] marked, CutPhase cp)    // 计算 s - t 的最小割,称为 maximum adjacency (cardinality) search
 62     {
 63         IndexMaxPQ<Double> pq = new IndexMaxPQ<Double>(G.V());      // 用于挑选权值最大的点用于合并
 64         pq.insert(cp.s, Double.POSITIVE_INFINITY);                  // 顶点 s 自己的权值为 +∞
 65         for (int v = 0; v < G.V(); v++)                             // 其他顶点权值初始化为 0
 66         {
 67             if (v != cp.s && !marked[v])
 68                 pq.insert(v, 0.0);
 69         }
 70         for (; !pq.isEmpty();)
 71         {
 72             cp.s = cp.t;                                            // 记录最后取出的两个顶点,每次往前挪一格
 73             cp.t = pq.delMax();                                     // 取走权值最大的顶点 v
 74             for (Edge e : G.adj(cp.t))                              // 只要 cp.t 的邻居还在 pq 中没有被取走,就更新邻居的权值
 75             {
 76                 int w = e.other(cp.t);
 77                 if (pq.contains(w))                                 
 78                     pq.increaseKey(w, pq.keyOf(w) + e.weight());    // 顶点 w 的权值自增边 e 的权值
 79             }
 80         }
 81         cp.weight = 0.0;                                            // 计算最后加入 T 的顶点的权值,即为最小割值
 82         for (Edge e : G.adj(cp.t))
 83             cp.weight += e.weight();
 84         return cp;
 85     }
 86 
 87     private EdgeWeightedGraph contractEdge(EdgeWeightedGraph G, int s, int t)   // 把顶点 t 合并到顶点 s,更新其他边的权值
 88     {
 89         EdgeWeightedGraph H = new EdgeWeightedGraph(G.V());         // 合并后的图,顶点与原来相同
 90         for (int v = 0; v < G.V(); v++)
 91         {
 92             for (Edge e : G.adj(v))                                 // 依顶点序遍历所有边
 93             {
 94                 int w = e.other(v);
 95                 if (v == s && w == t || v == t && w == s)           // 边 s-t 自身不要
 96                     continue;
 97                 if (v < w)                                          // 只考虑后向边,滤掉重复
 98                 {
 99                     if (w == t)                                     // 远端顶点 w 是被合并的 t,边 v - w(t) 替换成边 v - s
100                         H.addEdge(new Edge(v, s, e.weight()));
101                     else if (v == t)                                // 近端顶点 v 是被合并的 t,边 v(t) - w 替换成边 s - w
102                         H.addEdge(new Edge(w, s, e.weight()));
103                     else                                            // 边 v - w 与 s 或 t 无关,原样放进 H
104                         H.addEdge(new Edge(v, w, e.weight()));
105                 }
106             }
107         }
108         return H;
109     }
110 
111     public static void main(String[] args)
112     {
113         In in = new In(args[0]);
114         EdgeWeightedGraph G = new EdgeWeightedGraph(in);
115         class01 mc = new class01(G);
116         StdOut.print("Min cut: ");
117         for (int v = 0; v < G.V(); v++)
118         {
119             if (mc.cut(v))
120                 StdOut.print(v + " ");
121         }
122         StdOut.println("\nMin cut weight = " + mc.weight());
123     }
124 }

● 二分图最大匹配(最小顶点覆盖)的交替路径算法

  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\n", matching.size());
193         StdOut.printf("Number of vertices in min vertex cover = %d\n", matching.size());
194         StdOut.printf("Graph has a perfect matching           = %b\n", 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("\nMin 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\n", matching.size());
163         StdOut.printf("Number of vertices in min vertex cover = %d\n", matching.size());
164         StdOut.printf("Graph has a perfect matching           = %b\n", 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("\nMin 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 }

猜你喜欢

转载自www.cnblogs.com/cuancuancuanhao/p/10066440.html