网络流相关算法总结,最大流EK算法,SAP算法,最小费用最大流,最小费用路算法,最大流最小割定理

网络流相关定义

  1. 网络是一个有向带权图,包含一个源点和一个汇点,没反平行边
  2. 网络流:即网络上的流,是定义在边集E上的一个非负函数 flow={flow(u,v)} , flow(u,v) 是边上的流量
  3. 可行流:满足容量约束流量守恒的网络流
  4. 网络最大流:满足容量约束流量守恒的条件下,在流网络中找到一个净输出最大的网络流

最大流

求解最大流的整体思路是福特福克森算法。该算法的思想是:
在残余网络中找可增广路,然后在实流网络中沿着可增广路增流,在残余网络中沿着可增广路减流,;继续在残余网络中找可增广路。直到不存在可增广路位置。此时,实流网络中的可行流就是所求的最大流.

增广路定理:设 flow 是网络 G 的一个可行流,如果不存在从源点S到汇点 t 关于flow的可增广路 p ,则flow G 的一个最大流

福特福克森也是能算是一种求解最大流的思想,并不是具体的算法。下面说一下具体实现的算法。
实现的算法有三种:EK算法,Dinic算法,SAP算法+gap优化

我说一下EK算法和SAP算法。

EK算法

邻接矩阵存储残余网络,利用一个队列q来存放已访问未检查的点,vis[]数组来标记当前点是否被访问过,pre[]数组来记录前驱节点。

利用BFS来寻找可增广路,当存在一条可增广路时。从汇点一直往前面找,一直找到起点,在路径上找到一条可增广量最小的边,然后从起点到汇点,残余网络中正向沿着可增广路增流,反向沿着可增广路减流,然后不断地进行BFS,把每一次的可增光量累加起来,就是最大流

复杂度:O(VE2)

代码:
可以AC:POJ1273 Drainage Ditches(网络流–最大流,EK增广路算法)

#include <cstring>
#include <cctype>
#include <stdlib.h>
#include <string>
#include <map>
#include <iostream>
#include <stack>
#include <cmath>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
#define inf 0x3f3f3f3f
#define mem(a,b) memset(a,b,sizeof(a))
const int N=200+20;
int g[N][N],f[N][N],pre[N];//分别代表残余网络,实流网络,前驱数组
bool vis[N];//标记数组
int n,m;//点数和边数
bool bfs(int s,int t)
{
    mem(pre,-1);
    mem(vis,false);
    queue<int>q;
    vis[s]=true;
    q.push(s);
    while(!q.empty())
    {
        int now=q.front();
        q.pop();
        for(int i=1; i<=n; i++)
        {
            if(!vis[i]&&g[now][i]>0)
            {
                vis[i]=true;
                pre[i]=now;
                if(i==t)
                    return true;
                q.push(i);
            }
        }
    }
    return false;
}
int EK(int s,int t)
{
    int v,w,d,maxflow=0;
    while(bfs(s,t))
    {
        v=t;
        d=inf;
        while(v!=s)
        {
            w=pre[v];
            d=min(d,g[w][v]);
            v=w;
        }
        maxflow+=d;
        v=t;
        while(v!=s)
        {
            w=pre[v];
            g[w][v]-=d;
            g[v][w]+=d;
            if(f[v][w]>0)
                f[v][w]-=d;
            else
                f[w][v]+=d;
            v=w;
        }
    }
    return maxflow;
}
int main()
{
    int a,b,c;
    while(~scanf("%d%d",&m,&n))
    {
        mem(g,0);
        mem(f,0);
        for(int i=1; i<=m; i++)
        {
            scanf("%d%d%d",&a,&b,&c);
            g[a][b]+=c;
        }
        printf("%d\n",EK(1,n));
    }
    return 0;
}

SAP重贴标签算法

在这个算法中,我们用邻接表存储混合网络(正向边显示,cap,flow,反向边也显示这个).

这个算法的思想是,我们给广搜的网络标一个高度,每一层一个高度,最后一层的高度是0,然后按照广搜的层次,到起点依次递增。然后搜索的时候直接按照标签的告诉从高到低找,这样可以加快效率,当走的点的标签高度走不动的时候,我们采取重贴标签的策略,令当前节点的高度=所有邻接点高度的最小值+1,如果没有邻接边,则令当前节点的高度=节点数(注意:一定要保证容量>流量),然后不断的进行贴标签,重贴标签的过程,累积可增广量,最后的值就是最大流

我们需要进行gap优化:利用一个数组g[]来记录,当前的高度的节点有多少个,当重贴标签后发现当前高度的点只有一个那么就可以提前结束程序

算法复杂度: O(V2E)

下面是代码:
可以AC: P3376 【模板】网络最大流

#include <cstdio>
#include <cstring>
#include <cctype>
#include <stdlib.h>
#include <string>
#include <map>
#include <iostream>
#include <stack>
#include <cmath>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
#define inf 0x3f3f3f3f
#define mem(a,b) memset(a,b,sizeof(a))
const int N=10000+20;
const int M=100000+20;
int top;
int h[N],pre[N],g[N];//h[i]记录每个节点的高度,pre[i]记录前驱,g[i]表示距离为i个节点数有多少个
int first[N];
struct node
{
    int v,next;
    int cap,flow;
} E[M*2];
void init()
{
    mem(first,-1);
    top=0;
}
void add_edge(int u,int v,int c)
{
    E[top].v=v;
    E[top].cap=c;
    E[top].flow=0;
    E[top].next=first[u];
    first[u]=top++;
}
void add(int u,int v,int c)
{
    add_edge(u,v,c);
    add_edge(v,u,0);
}

void set_h(int t)//标高函数,从终点向起点标高
{
    queue<int>q;
    mem(h,-1);
    mem(g,0);
    h[t]=0;
    q.push(t);
    while(!q.empty())
    {
        int v=q.front();
        q.pop();
        g[h[v]]++;//当前高度的个数+1
        for(int i=first[v]; ~i; i=E[i].next)
        {
            int u=E[i].v;
            if(h[u]==-1)//当前节点未标高
            {
                h[u]=h[v]+1;
                q.push(u);
            }
        }
    }
}

int Isap(int s,int t,int n)//isap算法
{
    set_h(t);
    int ans=0,u=s;
    int d;
    while(h[s]<n)//节点的高度小于顶点数
    {
        int i=first[u];
        if(u==s) d=inf;
        for(; ~i; i=E[i].next)
        {
            int v=E[i].v;
            if(E[i].cap>E[i].flow&&h[u]==h[v]+1)//容量大于流量且当前的高度等于要去的高度+1
            {
                u=v;
                pre[v]=i;
                d=min(d,E[i].cap-E[i].flow);//找最小增量
                if(u==t)//到达汇点
                {
                    while(u!=s)
                    {
                        int j=pre[u];//找到u的前驱
                        E[j].flow+=d;//正向流量+d
                        E[j^1].flow-=d;//反向流量-d
                        u=E[j^1].v;//向前搜索
                    }
                    ans+=d;
                    d=inf;
                }
                break;
            }
        }
        if(i==-1)//邻接边搜索完毕,无法行进
        {
            if(--g[h[u]]==0)//重要的优化,这个高度的节点只有一个,结束
                break;
            int hmin=n-1;//重贴标签的高度初始为最大
            for(int j=first[u]; ~j; j=E[j].next)
            {
                if(E[j].cap>E[j].flow)
                    hmin=min(hmin,h[E[j].v]);//取所有邻接点高度的最小值
            }
            h[u]=hmin+1;//重新标高
            g[h[u]]++;//标高后该高度的点数+1
            if(u!=s)//不是源点时,向前退一步,重新搜
                u=E[pre[u]^1].v;
        }
    }
    return ans;
}
int main()
{
    int n,m,u,v,w,s,t;
    scanf("%d%d%d%d",&n,&m,&s,&t);
    init();
    for(int i=1; i<=m; i++)
    {
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);
    }
    printf("%d\n",Isap(s,t,n));
    return 0;
}

kuangbin的SAP模板

const int N=1000+20;
const int M=2*100000+20;
int top;
int h[N],pre[N],g[N],first[N],cur[N];//h[i]记录每个节点的高度,pre[i]记录前驱,g[i]表示距离为i个节点数有多少个
struct node
{
    int v,next,cap;
} E[M];
void init()
{
    mem(first,-1);
    top=0;
}
void add_edge(int u,int v,int c)
{
    E[top].v=v;
    E[top].cap=c;
    E[top].next=first[u];
    first[u]=top++;
    E[top].v=u;
    E[top].cap=0;
    E[top].next=first[v];
    first[v]=top++;
}
int sap(int start,int end,int nodenum)
{
    memset(h,0,sizeof(h));
    memset(g,0,sizeof(g));
    memcpy(cur,first,sizeof(first));
    int u=pre[start]=start,maxflow=0,aug=-1;
    g[0]=nodenum;
    while(h[start]<nodenum)
    {
loop:
        for(int &i=cur[u]; i!=-1; i=E[i].next)
        {
            int v=E[i].v;
            if(E[i].cap&&h[u]==h[v]+1)
            {
                if(aug==-1||aug>E[i].cap)
                    aug=E[i].cap;
                pre[v]=u;
                u=v;
                if(v==end)
                {
                    maxflow+=aug;
                    for(u=pre[u]; v!=start; v=u,u=pre[u])
                    {
                        E[cur[u]].cap-=aug;
                        E[cur[u]^1].cap+=aug;
                    }
                    aug=-1;
                }
                goto loop;
            }
        }
        int mindis=nodenum;
        for(int i=first[u]; i!=-1; i=E[i].next)
        {
            int v=E[i].v;
            if(E[i].cap&&mindis>h[v])
            {
                cur[u]=i;
                mindis=h[v];
            }
        }
        if((--g[h[u]])==0)break;
        g[h[u]=mindis+1]++;
        u=pre[u];
    }
    return maxflow;
}

最小费用最大流

在求最大流的过程中,对于每一条边都有一个费用,现在希望费用最小,流最大。求最大流和最小费用。

最小费用路算法

最小费用路算法的思想是:先找最小费用路,在该路径上面增流,增加到最大流。

利用邻接表建立双向边,正向边的花费为cost,反向边的花费为-cost.
在找最小费用路的时候,从源点出发,沿着可行 (E[i].cap>E[i].flow)广度搜索每个邻接点, 如果当前的边可以继续松弛,那么就更新,就是SPFA算法,并且记录一下前驱。

当找到最小费用路以后,那么就从汇点向源点找一条,最小的可增流量,沿着增广路正向增流,反向减流,最后花费的费用为:

mincost=dis[V]d

累加这个过程求出来的就是最小费用,最大流就是每次累加的可增流量

可以AC:【模板】最小费用最大流
算法复杂度: O(V2E)

#include <cstdio>
#include <cstring>
#include <cctype>
#include <stdlib.h>
#include <string>
#include <map>
#include <iostream>
#include <stack>
#include <cmath>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
#define inf 1000000
#define mem(a,b) memset(a,b,sizeof(a))
const int N=5000+20;
const int M=50000+20;

int top;//当前边下标
int dis[N],pre[N];//源点到点i的最小距离,pre[i]记录前驱
bool vis[N];//标记数组
int maxflow;
int first[N];//存储头结点
struct Edge
{
    int v,next;
    int cap,flow,cost;
} E[M*2];

void init()
{
    mem(first,-1);
    top=0;
    maxflow=0;
}

void add_edge(int u,int v,int c,int cost)
{
    E[top].v=v;
    E[top].cap=c;
    E[top].flow=0;
    E[top].cost=cost;
    E[top].next=first[u];
    first[u]=top++;
}
void add(int u,int v,int c,int cost)
{
    add_edge(u,v,c,cost);
    add_edge(v,u,0,-cost);
}
bool spfa(int s,int t,int n)
{
    int i,u,v;
    queue<int>q;
    mem(vis,false);
    mem(pre,-1);
    for(int i=1; i<=n; i++) dis[i]=inf;
    vis[s]=true;
    dis[s]=0;
    q.push(s);
    while(!q.empty())
    {
        u=q.front();
        q.pop();
        vis[u]=false;
        for(int i=first[u]; i!=-1; i=E[i].next)
        {
            v=E[i].v;
            if(E[i].cap>E[i].flow&&dis[v]>dis[u]+E[i].cost)
            {
                dis[v]=dis[u]+E[i].cost;
                pre[v]=i;
                if(!vis[v])
                {
                    q.push(v);
                    vis[v]=true;
                }
            }
        }
    }
    if(dis[t]==inf)
        return false;
    return true;
}
int MCMF(int s,int t,int n)//minCostMaxFlow
{
    int d;
    int i,mincost=0;//maxflow当前最大流量,mincost当前最小费用
    while(spfa(s,t,n))//表示找到了从s到t的最小费用路
    {
        d=inf;
        for(int i=pre[t]; i!=-1; i=pre[E[i^1].v]) //遍历反向边
            d=min(d,E[i].cap-E[i].flow);
        maxflow+=d;//更新最大流
        for(int i=pre[t]; i!=-1; i=pre[E[i^1].v]) //增广路上正向边流量+d,反向边流量-d
        {
            E[i].flow+=d;
            E[i^1].flow-=d;
        }
        mincost+=dis[t]*d;//dis[t]为该路径上单位流量费用之和
    }
    return mincost;
}
int main()
{
    int n,m,st,ed;
    int u,v,w,c;
    scanf("%d%d%d%d",&n,&m,&st,&ed);
    init();
    for(int i=1; i<=m; i++)
    {
        scanf("%d%d%d%d",&u,&v,&w,&c);
        add(u,v,w,c);
    }
    int mincost=MCMF(st,ed,n);
    printf("%d %d\n",maxflow,mincost);
    return 0;
}

最大流最小割定理

主要是理解一些概念

  • 割:设Ci为网络N中一些弧的集合,若从N中删去Ci中的所有弧能使得从源点Vs到汇点Vt的路集为空集时,称Ci为Vs和Vt间的一个割。就是用一条线可以把一张有向图分割成两个不想交的集合,割的容量就是这一条线割到的边上的容量之和
  • 最小割:图中所有的割中,边权值和最小的割为最小割。就是用一条线把一个图分成两个部分,使得边上所有容量的和最小,如下图最小割的容量为:5+4+2=11
  • 最大流最小割定理:在任何的网络中,最大流的值等于最小割的容量。否则还可以增广。那么有一个特征:最小割个到的边的容量流量相等。
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值