最大流(二)---- 最大流以及最小费用最大流问题

由于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;//开销
    }
}

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值