1. 程式人生 > >演算法#18--最大流量問題(網路流演算法)

演算法#18--最大流量問題(網路流演算法)

1.物理模型

請想象一組相互連線大小不一的輸油管道,每根管道有它自己的流量和容量,問從起點到終點的最大流量是多少?如下流量圖中,深色路徑流量之和為最大路徑。如何求得,下面內容將詳細介紹。

2.數學模型

一個流量網路,是一張邊的權重(這裡稱為容量)為正的加權有向圖。一個st-流量網路有兩個已知的頂點,即起點s和終點t。

3.Ford-Fulkerson演算法

也稱為增廣路徑演算法。它的定義是:網路中的初始流量為零,沿著任意從起點到終點(且不含有飽和的正向邊或是空逆向邊)的增廣路徑增大流量,直到網路中不存在這樣的路徑為止。

也即,假設x為該路徑上的所有邊中未使用容量的最小值,那麼只需將所有邊的流量增大x,即可將網路中的總流量至少增大x。反覆這個過程,直到所有起點到終點的路徑上至少有一條邊是飽和的。

4.剩餘網路

這裡,與流量對應的邊的方向和流量本身相反。程式碼如下FlowNetwork類。

5.程式碼實現

Ford-Fulkerson演算法最簡單的實現可能就是最短增廣路徑演算法了(最短指的是路徑長度最小,而非流量或是容量)。增廣路徑的查詢等價於剩餘網路中的廣度優先搜尋(BFS)。


public class FordFulkerson 
{
    private boolean[] marked;   //在剩餘網路中是否存在從s到v的路徑
    private FlowEdge[] edgeTo;  //從s到v的最短路徑上的最後一條邊
    private
double value; //當前最大流量 public FordFulkerson(FlowNetwork G, int s, int t) { //找出從s到t的流量網路G的最大流量配置 while(hasAugmentingPath(G, s, t)) { //利用所有存在的增廣路徑 //計算當前的瓶頸容量 double bottle = Double.POSITIVE_INFINITY; for(int v = t; v != s; v = edgeTo[v].other(v)) { bottle = Math.min(bottle, edgeTo[v].residualCapacityTo(v)); } //增大流量
for(int v = t; v != s; v = edgeTo[v].other(v)) { edgeTo[v].addResidualFlowTo(v, bottle); } value += bottle; } } private boolean hasAugmentingPath(FlowNetwork G, int s, int t) { marked = new boolean[G.V()]; //標記路徑已知的頂點 edgeTo = new FlowEdge[G.V()]; //路徑上的最後一條邊 Queue<Integer> q = new Queue<Integer>(); marked[s] = true; //標記起點 q.enqueue(s); //並將它入列 while(!q.isEmpty()) { int v = q.dequeue(); for(FlowEdge e : G.adj(v)) { int w = e.other(v); if(e.residualCapacityTo(w) > 0 && !marked[w]) {//(在剩餘網路中)對於任意一條連線到一個未標記的頂點的邊 edgeTo[w] = e; //保持路徑上的最後一條邊 marked[w] = true; //標記w,因為路徑現在是已知的了 q.enqueue(w); //將它入列 } } } return marked[t]; } public double value() { return value; } public boolean inCut(int v) { return marked[v]; } public static void main(String[] args) { FlowNetwork G = new FlowNetwork(6); int[] from = new int[]{0, 0, 1, 1, 2, 2, 3, 4}; int[] to = new int[]{1, 2, 3, 4, 3, 4, 5, 5}; double[] capacity = new double[]{2.0, 3.0, 3.0, 1.0, 1.0, 1.0, 2.0, 3.0}; for(int i = 0; i < from.length; i++) { FlowEdge edge = new FlowEdge(from[i], to[i], capacity[i]); G.addEdge(edge); } int s = 0, t = G.V() - 1; FordFulkerson maxflow = new FordFulkerson(G, s, t); System.out.println("Max flow from " + s + " to " + t); for(int v = 0; v < G.V(); v++) { for(FlowEdge e : G.adj(v)) { if(v == e.from() && e.flow() > 0) { System.out.println(" " + e); } } } System.out.println("Max flow value = " + maxflow.value()); } }

輸出:

Max flow from 0 to 5
 0->2 3.00 2.00
 0->1 2.00 2.00
 1->4 1.00 1.00
 1->3 3.00 1.00
 2->4 1.00 1.00
 2->3 1.00 1.00
 3->5 2.00 2.00
 4->5 3.00 2.00
Max flow value = 4.0

public class FlowNetwork 
{
    private final int V;
    private int E;
    private Bag<FlowEdge>[] adj;

    @SuppressWarnings("unchecked")
    public FlowNetwork(int V)
    {
        this.V = V;
        this.E = 0;
        adj = (Bag<FlowEdge>[]) new Bag[V];
        for(int v = 0; v < V; v++)
        {
            adj[v] = new Bag<FlowEdge>();
        }
    }

    public int V()
    {
        return V;
    }

    public int E()
    {
        return E;
    }

    public void addEdge(FlowEdge e)
    {
        int v = e.either(), w = e.other(v);
        adj[v].add(e);
        adj[w].add(e);
        E++;
    }

    public Iterable<FlowEdge> adj(int v)
    {
        return adj[v];
    }

    public Iterable<FlowEdge> edges()
    {
        Bag<FlowEdge> b = new Bag<FlowEdge>();
        for(int v = 0; v < V; v++)
        {
            for(FlowEdge e : adj[v])
            {
                if(e.other(v) > v)
                {
                    b.add(e);
                }
            }
        }
        return b;
    }
}

public class FlowEdge 
{
    private final int v;
    private final int w;
    private final double capacity;
    private double flow;

    public FlowEdge(int v, int w, double capacity)
    {
        this.v = v;
        this.w = w;
        this.capacity = capacity;
        this.flow = 0;
    }

    public int from()
    {
        return v;
    }

    public int to()
    {
        return w;
    }

    public double capacity()
    {
        return capacity;
    }

    public double flow()
    {
        return flow;
    }

    public int either()
    {
        return v;
    }

    public int other(int vertex)
    {
        if(vertex == v)
        {
            return w;
        }
        else if(vertex == w)
        {
            return v;
        }
        else
        {
            throw new RuntimeException("Inconsistent edge");
        }
    }

    public double residualCapacityTo(int vertex)
    {
        if(vertex == v)
        {
            return flow;
        }
        else if(vertex == w)
        {
            return capacity - flow;
        }
        else
        {
            throw new RuntimeException("Inconsistent edge");
        }
    }

    public void addResidualFlowTo(int vertex, double delta)
    {
        if(vertex == v)
        {
            flow -= delta;
        }
        else if(vertex == w)
        {
            flow += delta;
        }
        else
        {
            throw new RuntimeException("Inconsistent edge");
        }
    }

    public String toString()
    {
        return String.format("%d->%d %.2f %.2f", v, w, capacity, flow);
    }
}