最大流(二)---- 最大流以及最小費用最大流問題

由於https://www.cnblogs.com/fzl194/p/8859308.html解決最小費用最大流問題,沒看太懂,所以下面給出自己理解的,容易理解,代碼容易實現,但是可能複雜度比較高的方式解決最大流問題和最小費用最大流問題

最大流問題

 

圖1位原始圖,現在求節點0到節點5的最大流

將圖1的圖結構按照圖2存儲,每條邊給一個反向邊,流量爲0

我們認爲該條邊的流量不爲0,則該條邊爲可通的邊,若該邊流量爲0,則該邊不可聯通,相當於距離爲無窮大

那麼我們需要找一條源點0至匯點5的鏈路,並記錄下該鏈路,如果能找到,那麼肯定就有流量可以分,這條也就是所謂的增廣路

在圖3黑色的邊爲找到的一條鏈路,我們根據記錄下的鏈路,找出鏈路上流量的最小值,該條鏈路上的所有邊減去該最小值,反向邊增加該最小值,結果如圖4所示

然後繼續找鏈路,如圖5,分配最小值,如圖6;再找鏈路,如圖7,再分最小值,如圖8;

此時再也找不到一條節點0到節點5的鏈路,那麼最大流也就找到了,如圖9,圖9邊的方向顛倒一下,即爲流量路線及分配的流量,圖10

下面給出簡單的實現,還可以有很多的優化

package graphModel;

import java.util.Arrays;
import java.util.HashSet;

public class MaxFlow {
    public static int inf = 1000;// 表示兩點之間不連接

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        int N = 6;// 實際節點個數
        int S = 0;
        int T = N - 1;

        int[][] dag = { { 0, 8, inf, 7, inf, inf }, { inf, 0, 9, 5, inf, inf }, { inf, inf, 0, 2, inf, 5 },
                { inf, inf, inf, 0, 9, inf }, { inf, inf, 6, inf, 0, 10 }, { inf, inf, inf, inf, inf, 0 } };

        // 前向星存儲圖結構
        Graph graph = new Graph(N);
        graph.initSolveMaxFlow(dag);
        Graph.Edge[] edges = graph.getEdges();
        int[] head = graph.getHead();
        int[][] indexOfEdge = graph.getIndexOfEdge();
        HashSet<Integer> set = new HashSet<>();// 防止深度遍歷時出現環路,導致一直轉圈

        // 初始化相關變量
        int flow = 0;
        int[] preNode = new int[N];// p[i]從原點s到終點t的節點i的前一節點的編號
        Arrays.fill(preNode, -1);

        while (dfs(edges, head, preNode, S, T, set)) {
            set.clear();
            // 根據preNode找出該路徑,並分配流量
            int cur = T;
            int pre = preNode[cur];
            int min = inf;
            while (pre != -1) {
                // 1.找到該鏈路權值的最小值
                if ( min > edges[indexOfEdge[pre][cur]].w)
                    min = edges[indexOfEdge[pre][cur]].w;
                cur = pre;
                pre = preNode[pre];
            }
            cur = T;
            pre = preNode[cur];
            while (pre != -1) {
                // 2.給鏈路分配最小值
                edges[indexOfEdge[pre][cur]].w -= min;
                // 位運算符 ^ :1^1=0  0^1=1  2^1=3  3^1=2.
                //爲了方便 ^ 運算符使用,我們可以提前建好反向邊,之後一條邊,^ 一下就是另一條邊
                edges[(indexOfEdge[pre][cur])^1].w += min;
                cur = pre;
                pre = preNode[pre];
            }
            flow += min;
        }
        System.out.println(flow);
    }

    public static boolean dfs(Graph.Edge[] edges, int[] head, int[] preNode, int S, int T, HashSet<Integer> set) {
        for (int i = head[S]; i != -1; i = edges[i].next) {
            int to = edges[i].to;
            if (edges[i].w > 0 && !set.contains(to)) {
                preNode[to] = S;
                set.add(S);
                if (to == T)
                    return true;
                else {
                    if (dfs(edges, head, preNode, to, T, set))
                        return true;
                }

            }
        }
        return false;
    }

}

最小費用最大流

(m,n)其中m表示最大流量,n表示單位流量的開銷。

解法與最大流類似,只需要將求解0到5的任意一條路徑,改爲求解0到5的最短路徑,然後分配流量,就可以啦。

下面我們使用迪傑斯特拉最短路徑算法解決該問題

package graphModel;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;

public class MincostMaxflow {
    public static int inf = 1000;
    public static int maxn = 1000;// 節點預分配的最大個數,防止越界
    public static void main(String[] args) {
        int N = 6;//實際節點個數
        int S = 0;
        int T = N-1;
        // 1.初始化圖結構
        Tuple[][] dag = { { new Tuple(0,0), new Tuple(8,2), new Tuple(inf,0), new Tuple(7,8), new Tuple(inf,0), new Tuple(inf,0) }, 
                { new Tuple(inf,0), new Tuple(0,0), new Tuple(9,2), new Tuple(5,5), new Tuple(inf,0), new Tuple(inf,0) }, 
                { new Tuple(inf,0), new Tuple(inf,0), new Tuple(0,0), new Tuple(2,1), new Tuple(inf,0), new Tuple(5,6) },
                { new Tuple(inf,0), new Tuple(inf,0), new Tuple(inf,0), new Tuple(0,0), new Tuple(9,3), new Tuple(inf,0) }, 
                { new Tuple(inf,0), new Tuple(inf,0), new Tuple(6,4), new Tuple(inf,0),new Tuple(0,0), new Tuple(10,7) }, 
                { new Tuple(inf,0), new Tuple(inf,0), new Tuple(inf,0), new Tuple(inf,0), new Tuple(inf,0), new Tuple(0,0) } };
        
        
        
        GraphMinMax graph = new GraphMinMax(N);
        graph.initSolveMaxFlow(dag);
        GraphMinMax.Edge[] edges = graph.getEdges();
        int[] head = graph.getHead();
        int[][] indexOfEdge = graph.getIndexOfEdge();
        
        // 初始化相關變量
        int flow = 0;
        int[] path = dijstra(edges,head,indexOfEdge,S,T,N);
        while (path!=null) {
            // 根據preNode找出該路徑,並分配流量
            int cur = T;
            int pre = path[cur];
            int min = inf;
            while (pre != -1) {
                // 1.找到該鏈路權值的最小值
                if ( min > edges[indexOfEdge[pre][cur]].f)
                    min = edges[indexOfEdge[pre][cur]].f;
                cur = pre;
                pre = path[pre];
            }
            cur = T;
            pre = path[cur];
            while (pre != -1) {
                // 2.給鏈路分配最小值
                edges[indexOfEdge[pre][cur]].f -= min;
                // 位運算符 ^ :1^1=0  0^1=1  2^1=3  3^1=2.
                //爲了方便 ^ 運算符使用,我們可以提前建好反向邊,之後一條邊,^ 一下就是另一條邊
                edges[(indexOfEdge[pre][cur])^1].f += min;
                
                cur = pre;
                pre = path[pre];
            }
            flow += min;
            path = dijstra(edges,head,indexOfEdge,S,T,N);
        }
       //輸出分配的流
        System.out.println(flow);
        for(int i=1; edges[i]!=null;i=i+2) {       
            System.out.println(edges[i].to+"-->"+edges[i-1].to+" "+edges[i].f);
        }
    }
    public static int[] dijstra(GraphMinMax.Edge[] edges, int[] head, int[][] indexOfEdge, int S, int T, int N) {
        if (S == T)
            return new int[] { S };
        // 2.根據源節點初始化d[]和path[]並初始化U
        HashSet<Integer> U = new HashSet<Integer>();// 存放剩餘頂點集
        int d[] = new int[N];// 存放源節點到其餘節點的最短路徑
        int path[] = new int[N];// 源節點到目的節點的最短路徑上,目的節點的前一箇中繼節點
        Arrays.fill(path, -1);
        Arrays.fill(d, inf);
        for (int i = head[S]; i != -1; i = edges[i].next) {
            if(edges[i].f>0) {
                int to = edges[i].to;
                path[to] = S;
                d[to] = edges[i].c;
            }
        }
        for(int i=0;i<N;i++) {
            if(i!=S)
                U.add(i);
        }
        while (!U.isEmpty()) {
            // 從剩餘集中找到距離最小值作爲中間節點
            int midKey = -1;
            int midValue = inf;
            for (int u : U) {
                if (d[u] < midValue) {
                    midValue = d[u];
                    midKey = u;
                }
            }
            if (midKey == -1)// 剩餘節點均爲不可達節點
                break;
            if (midKey == T)
                break;
            U.remove(midKey);

            for (int u : U) {// 更新d[]和path[]
                if (indexOfEdge[midKey][u]!=inf && d[u] > d[midKey] + edges[indexOfEdge[midKey][u]].c && edges[indexOfEdge[midKey][u]].f>0) {
                    d[u] = d[midKey] + edges[indexOfEdge[midKey][u]].c;
                    path[u] = midKey;
                }
            }
        }
        if (path[T] == -1)
            return null;
        else
            return path;
    }
}
package graphModel;

import java.util.Arrays;

import graphModel.Graph.Edge;

public class GraphMinMax {
    public static int inf = 1000;// 用於申請大數組,防止越界,同時也表示兩點之間不連接
    private Edge[] edges;// 存儲邊的數組
    private int[] head;// 鏈式前向星存儲圖結構
    private int cnt;// 用於鏈式前向星存儲圖結構  
    private int[][] indexOfEdge;//已知from和to節點,輸出from-to這條邊在edges數組內的索引

    public GraphMinMax(int N) {//N爲頂點個數
        edges = new Edge[N*(N-1)];
        head = new int[N];
        Arrays.fill(head, -1);
        indexOfEdge = new int[N][N];
        for(int i=0 ;i <N; i++) 
            for(int j=0; j<N; j++)
                indexOfEdge[i][j]=inf;
        this.cnt = 0;
    }

    public void addEdge(int u, int v, int f, int c) {
        edges[cnt] = new GraphMinMax.Edge();
        edges[cnt].f = f;
        edges[cnt].c = c;
        edges[cnt].to = v;
        edges[cnt].next = head[u];
        indexOfEdge[u][v] = cnt;
        head[u] = cnt;
        cnt++;
    }

    /**
     * 將鄰接矩陣存儲的圖轉爲鏈式前向星存儲 用於解決最大流問題
     * 
     * @param dag
     */
    public void initSolveMaxFlow(Tuple<Integer,Integer>[][] dag) {
        for (int i = 0; i < dag.length; i++) {
            for (int j = dag.length - 1; j >= 0; j--) {
                if (i != j && dag[i][j].getKey() < inf) {
                    this.addEdge(i, j, dag[i][j].getKey(),dag[i][j].getValue());
                    this.addEdge(j, i, 0,dag[i][j].getValue());
                }
            }
        }
    }


    public Edge[] getEdges() {
        return this.edges;
    }

    public int[] getHead() {
        return this.head;
    }
    public int[][] getIndexOfEdge(){
        return this.indexOfEdge;
    }

    class Edge {
        Edge() {
        }

        int next;
        int to;
        int f;//流量
        int c;//開銷
    }
}

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章