残留网络
在介绍最大流算法之前先介绍一下什么是残留网络。残余网络的概念有点类似于集合中的补集概念。
下图是残余网络的样例。
上面的网络是原始网络。以下的网络是计算出的残留网络。残留网络的作用就是用来描写叙述这个网络中还剩下多少能够利用的流量。
流量网络
最大流算法比曾经介绍的算法都要复杂。
网络中的每一条边须要记录容量和当前流量。容量是固定值,是已知条件,而当前流量在计算过程中会一直发生变化。因此,须要建立一个专门的类,用于最大流算法。
public class FlowEdge { private int v; private int w; private double capacity; private double flow; public FlowEdge(int v, int w, double capacity) { this.v = v; this.w = w; this.capacity = capacity; } public int from() { return v; } public int to() { return w; } public double capactity() { return capacity; } public double flow() { return flow; } public int other(int v) { if (v == this.v) return this.w; else return this.v; } // 返回可以添加的最大流量 public double residualCapacityTo(int v) { if (v == this.v) return flow; else return capacity - flow; } // 添加这条边的流量 public void addResidualFlowTo(int v, double d) { if (v == this.v) flow -= d; else flow += d; } }
与其它的图论算法类似,须要将图中全部的边替换成FlowEdge。于是得到了例如以下的类。
import java.util.LinkedList; import java.util.List; public class FlowNetwork { private int V; private List<FlowEdge>[] adj; public FlowNetwork(int V) { this.V = V; adj = new LinkedList[V]; for (int i = 0; i < adj.length; i++) { adj[i] = new LinkedList(); } } public Iterable<FlowEdge> adj(int v) { return adj[v]; } public int V() { return V; } public void addEdge(FlowEdge e) { int v = e.from(); adj[v].add(e); } @Override public String toString() { String result = ""; for (int i = 0; i < V; i++) { result += i + ":"; for(FlowEdge e:adj[i]) { result += " " + e.toString(); } result += " "; } return result; } }
算法类
依照惯例,须要为最大流算法编写一个专门的类。
该类的代码例如以下:
import java.util.LinkedList; import java.util.Queue; public class FordFulkerson { private FlowEdge[] edgeTo; private double value; public FordFulkerson(FlowNetwork G, int s, int t) { // 一直添加流量直到无法再添加为止 while (hasAugmentingPath(G, s, t)) { // 找出增广路的瓶颈 double bottle = Double.POSITIVE_INFINITY; int v = t; while (v != s) { bottle = Math.min(bottle, edgeTo[v].residualCapacityTo(v)); v = edgeTo[v].other(v); } // 添加整条路径的流量 v = t; while (v != s) { edgeTo[v].addResidualFlowTo(v, bottle); v = edgeTo[v].other(v); } // 最大流添加 value += bottle; } } public double value() { return value; } // 推断是否有增广路 // 有增广路的条件就是存在一条路径,这条路径上全部的边都能添加流量。private boolean hasAugmentingPath(FlowNetwork G, int s, int t) { edgeTo = new FlowEdge[G.V()]; // 注意,这句话是必需要有的。由于每次增广路径都不一样。 boolean[] visited = new boolean[G.V()]; // BFS Queue<Integer> q = new LinkedList<Integer>(); q.add(s); visited[s] = true; // 注意:这句话不要遗漏 while (!q.isEmpty()) { int v = q.poll(); // 可以通过的条件是流量可以添加 for (FlowEdge e : G.adj(v)) { int w = e.other(v); if (e.residualCapacityTo(w) > 0 && !visited[w]) { edgeTo[w] = e; q.add(w); visited[w] = true; } } } // 有增广路的条件就是S点可以到达T点。 return visited[t]; } public static void main(String[] argv) { FlowNetwork g = new FlowNetwork(4); int[] data = {0, 1, r(), 0, 2, r(), 2, 1, r(), 1, 3, r(), 2, 3, r(), 0, 3, r()}; for (int i = 0; i < data.length; i += 3) { g.addEdge(new FlowEdge(data[i], data[i + 1], data[i + 2])); } StdOut.println(new FordFulkerson(g, 0, 3).value()); } private static int r() { return StdRandom.uniform(1000); } }