最大流, 最小割问题及算法实现

最大流(MaxFlow)问题

给定指定的一个有向图,其中有两个特殊的点源S(Sources)和汇T(Sinks),每条边有指定的容量(Capacity),求满足条件的从S到T的最大流(MaxFlow).

想象一条多条不同水流量的水管组成的网络, s为供水广, t为水用户, 最大流问题就是找到能够在s到t流通的最大水流量

一个流是最大流当且仅当其残存网络不包含任何增广路径(里面的名称在后面有详细解释)

流(Flow)的基本性质

设$C_{uv}$代表边u到v最大允许流量(Capacity), $f_{uv}$代表u到v的当前流量, 那么有一下两个性质:

  • $(u, v)$为有向图边, $0<=f_{uv}<=C_{uv}$, 即对于所有的边, 当前流量不允许超过其Capacity
  • 除了$s, t$之外, 对所有节点有 $\sum\limits_{(v, u)}f_{wu} = \sum\limits_{(u, v)}f_{uv}$, 即对于任何一点, 流入该点的流量等于留出该点的流量, 流量守恒原则(类似与能量守恒的概念).

非负数值$f(u, v)$为从节点u到节点v的流.一个流$|f|$的定义: $$|f| = \sum\limits_{v \in V}f(s,v) - \sum\limits_{v \in V}f(v, s)$$

最大流问题即要找到一个最大的流f

Ford-Fulkerson方法

之所以称之为方法, 而不是算法, 因为FF(Ford-Fulkerson简称)包含不同运行时间的几种实现, 是一种迭代的方法.

该方法主要依赖于残存网络, 增广路径和割

//伪代码
初始化:所有流f = 0
while 在残存网络中存在增广路径p
    增加流f的值
return f

残存网络

给定网络G和流量f, 残存网络$G_f$由那些仍有空间对流量进行调整的边构成.

残留网络 = 容量网络capacity - 流量网络flow

残存网络

增广路径

增广路径p是残存网络中一条从源节点s到汇点t的简单路径,在一条增广路径p上能够为每条边增加的流量的最大值为路径p的残存容量$c_f(p) = min \{c_f(u,v):(u,v) \in p \}$

在一条增广路径p上, 要增加整条增广路径的水流量, 则必须看最小能承受水流量的管道, 不然水管会爆掉, 这最小承受水流量就是残存容量

在有向图网络G中, 割(S, T)将V划分为S和T = V - S, 使得s属于S集合, t属于T集合. 割(S, T)的容量是指从集合S到集合T所有边的容量之和.

最大流

最大流最小割理论

设$f$为流网络G = (V, E)中的一个流, 该流网络的源节点为s, 汇点为t, 则下面的条件是等价的:

  • f是G的一个最大流
  • 残存网络$G_f$不包含任何增广路径
  • $|f| = c(S, T)$, 其中(S, T)是流网络G的某个割

Ford-Fulkerson算法Java实现

伪代码

for each edge(u, v)属于G.E(图G的边)
    (u, v).f = 0  //所有边的流为0
//循环终止条件为残存我昂罗中不存在增广路径
while s到t的残存网络中存在增广路径p:
    c(p) = 最小残存容量
    for 增广路径的每条边
        if 这条边属于E集合
            (u, v).f = (u, v).f + c(p)  //意思是在原有的流增加最小残存容量.
        else
            (u, v).f = (u, v).f - c(p)
//边的定义
public class FlowEdge {
    private final int v, 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;
    }
    public int from() {
        return v;
    }
    public int to() {
        return w;
    }
    public double capacity() {
        return capacity;
    }
    public double flow() {
        return flow;
    }
    public int other(int vertex) {
        if (vertex == v) {
            return w;
        } else if (vertex == w) {
            return v;
        } else {
            throw new RuntimeException("Inconsistent edge");
        }
    }
    //v中残留流量
    public double residualCapacityTo(int vertex) {
        if (vertex == v) {  //反向边
            return flow;
        } else if (vertex == w) {  //正向边
            return capacity - flow;
        } else {
            throw new IllegalArgumentException();
        }
    }
    //向v中增加delta
    public void addResidualFlowTo(int vertex, double delta) {
        if (vertex == v) {
            flow -= delta;
        } else if (vertex == w) {
            flow += delta;
        } else {
            throw new IllegalArgumentException();
        }
    }
}
//流图的定义
public class FlowNetwork {
    private final int V;  //顶点个数
    private Bag<FlowEdge>[] adj;
    public FlowNetwork(int V) {
        this.V = V;
        adj = (Bag<FlowEdge>[]) new Bag[V];
        for (int v = 0; v < V; v++) {
            adj[v] = new Bag<>();
        }
    }
    //想流图中增加边
    public void addEdge(FlowEdge e) {
        int v = e.from();
        int w = e.to();
        adj[v].add(e);  //正向边
        adj[w].add(e);  //反向边
    }
    public int V() {
        return V;
    }
    public Iterable<FlowEdge> adj(int v) {  //返回邻接边
        return adj[v];
    }
}
//FordFulkerson方法的实现
public class FordFulkerson {
    private boolean[] marked;  //如果残留网络中有s->v路径, 则为true
    private FlowEdge[] edgeTo;  //s->v路径的最后的边
    private double value; //流

    public FordFulkerson(FlowNetwork G, int s, int t) {
        value = 0.0;
        //当找不到增广路径时终止
        while (hasAugmentingPaht(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 hasAugmentingPaht(FlowNetwork G, int s, int t) {
        edgeTo = new FlowEdge[G.V()];
        marked = new boolean[G.V()];

        Queue<Integer> q = new Queue<>();
        q.enqueue(s);
        marked[s] = true;
        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;
                    q.enqueue(w);
                }
            }
        }
        return marked[t];
    }
    public double value() {
        return value;
    }
    public boolean intCut(int v) { //在残留网络中v->s是否可达
        return marked[v];
    }
}
时间: 2024-10-13 19:52:18

最大流, 最小割问题及算法实现的相关文章

最大流-最小割 MAXFLOW-MINCUT ISAP

简单的叙述就不必了. 对于一个图,我们要找最大流,对于基于增广路径的算法,首先必须要建立反向边. 反向边的正确性: 我努力查找了许多资料,都没有找到理论上关于反向边正确性的证明. 但事实上,我们不难理解,对于每条反向边,我们流过它相当于撤销了一条正向边的流量. 并且它是必须的: 而且从理论上,我们在加入反向边之后得到的最大流,我们从残余网络考虑. 我们要认识到,反向边不会使最大流流量减少,这是很显然的.有flow<=flow'. 接下来我们考虑所有点的流量是否可以只用正向边得到. 并且我们考察汇

图的全局最小割的Stoer-Wagner算法及例题

Stoer-Wagner算法基本思想:如果能求出图中某两个顶点之间的最小割,更新答案后合并这两个顶点继续求最小割,到最后就得到答案. 算法步骤: ------------------------------------------------------------------------------------------------------------------------- (1)首先初始化,设最小割ans = INF                                

zoj3792--Romantic Value(最大流+最小割,求解割边)

Romantic Value Time Limit: 2 Seconds      Memory Limit: 65536 KB Farmer John is a diligent man. He spent a lot of time building roads between his farms. From his point of view, every road is romantic because the scenery along it is very harmonious an

hdu 3987 Harry Potter and the Forbidden Forest【网路流最小割模型】

Harry Potter and the Forbidden Forest Time Limit: 5000/3000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Total Submission(s): 1549    Accepted Submission(s): 528 Problem Description Harry Potter notices some Death Eaters try to slip

UVA 11248 Frequency Hopping (最大流+最小割)

题意:与正常的网络流一样,不过给定的第一行的最后一个数C的意思是能能否在给定的图里求出修改某一条边或者不修改某一条边是的这个图的流变成C,如果没修改就能有C,那么输出possible,通过修改能得到C输出possible+能修改的边集合,否则输出no possible 思路:(自己的是死暴力方法,直接爆了,想了很多法子都来不起,最后参照白书的思路来起了)可以先求出最大流,然后求出最小割里的弧,依次修改最小割里的弧,看能求出的最大流是否大于C PS:不优化的话很容易超时,第一个优化是把第一次求得得

【图割】opencv中构建图和最大流/最小割的源码解读

#include <vector> using namespace std; #define MIN(a,b) (((a)<(b))?(a):(b)) typedef unsigned char uchar; template <class TWeight> class GCGraph { public: GCGraph(); GCGraph(unsigned int vtxCount, unsigned int edgeCount); ~GCGraph(); void cr

POJ 2914 - Minimum Cut - 全局最小割,Stoer-Wagner算法

题目大意:给定一个N个点.M条边的无向带权图,边的权值均为正整数.若要使它变成非连通图,需要移除的边总权值最小是多少? N≤500,图中不存在自环,但可能有重边(这里题意没交代清楚). Stoer-Wagner算法裸题.英文维基:https://en.wikipedia.org/wiki/Stoer%E2%80%93Wagner_algorithm 该算法的思想之一是:对于一个无向连通图,选定某两点s,t,以及该图的一个s-t割C,则"C是该图的全局最小割"是"C是s-t的最

bzoj1412-网络流最小割

狼爱上羊啊爱的疯狂,谁让他们真爱了一场:狼爱上羊啊并不荒唐,他们说有爱就有方向......” Orez听到这首歌,心想:狼和羊如此和谐,为什么不尝试羊狼合养呢?说干就干! Orez的羊狼圈可以看作一个n*m个矩阵格子,这个矩阵的边缘已经装上了篱笆.可是Drake很快发现狼再怎么也是狼,它们总是对羊垂涎三尺,那首歌只不过是一个动人的传说而已.所以Orez决定在羊狼圈中再加入一些篱笆,还是要将羊狼分开来养. 通过仔细观察,Orez发现狼和羊都有属于自己领地,若狼和羊们不能呆在自己的领地,那它们就会变

ISAP 最大流 最小割 模板

虽然这道题用最小割没有做出来,但是这个板子还是很棒: #include<stdio.h> #include<math.h> #include<string.h> #include<iostream> #include<algorithm> #define ll long long using namespace std; #define REP( i , a , b ) for ( int i = a ; i < b ; ++ i ) #d