EK算法

    本文的目标群体是网络流的初学者,尤其是看了各种NB的教程也没看懂怎么求最大流的小盆友们。本文的目的是,解释基本的网络流模型,最基础的最大流求法,即bfs找增广路法,也就是EK法,全名是Edmond-Karp,其实我倒是觉得记一下算法的全名和来历可以不时的拿出来装一装。

    比如说这个,EK算法首先由俄罗斯科学家Dinic在1970年提出,没错,就是dinic算法的创始人,实际上他提出的也正是dinic算法,在EK的基础上加入了层次优化,这个我们以后再说,1972年Jack Edmonds和Richard Karp发表了没有层次优化的EK算法。但实际上他们是比1790年更早的时候就独立弄出来了。

    你看,研究一下历史也是很有趣的。

    扯远了,首先来看一下基本的网络流最大流模型。

    有n个点,有m条有向边,有一个点很特殊,只出不进,叫做源点,通常规定为1号点。另一个点也很特殊,只进不出,叫做汇点,通常规定为n号点。每条有向边上有两个量,容量和流量,从i到j的容量通常用c[I,j]表示,流量则通常是f[I,j]。通常可以把这些边想象成道路,流量就是这条道路的车流量,容量就是道路可承受的最大的车流量。很显然的,流量<=容量。而对于每个不是源点和汇点的点来说,可以类比的想象成没有存储功能的货物的中转站,所有”进入”他们的流量和等于所有从他本身”出去”的流量。

    把源点比作工厂的话,问题就是求从工厂最大可以发出多少货物,是不至于超过道路的容量限制,也就是,最大流。

    比如这个图。每条边旁边的数字表示它的容量。

 

最大流模板【EdmondsKarp算法,简称EK算法,O(m^2n)】



    下面我们来考虑如何求最大流。

    首先,假如所有边上的流量都没有超过容量(不大于容量),那么就把这一组流量,或者说,这个流,称为一个可行流。一个最简单的例子就是,零流,即所有的流量都是0的流。

我们就从这个零流开始考虑,假如有这么一条路,这条路从源点开始一直一段一段的连到了汇点,并且,这条路上的每一段都满足流量<容量,注意,是严格的<,而不是<=。那么,我们一定能找到这条路上的每一段的(容量-流量)的值当中的最小值delta。我们把这条路上每一段的流量都加上这个delta,一定可以保证这个流依然是可行流,这是显然的。

    这样我们就得到了一个更大的流,他的流量是之前的流量+delta,而这条路就叫做增广路。

    我们不断地从起点开始寻找增广路,每次都对其进行增广,直到源点和汇点不连通,也就是找不到增广路为止。当找不到增广路的时候,当前的流量就是最大流,这个结论非常重要。

寻找增广路的时候我们可以简单的从源点开始做bfs,并不断修改这条路上的delta量,直到找到源点或者找不到增广路。

这里要先补充一点,在程序实现的时候,我们通常只是用一个c数组来记录容量,而不记录流量,当流量+1的时候,我们可以通过容量-1来实现,以方便程序的实现。

 

Bfs过程的半伪代码:下面另给一个C++版的模板

int BFS()
{
    int i,j,k,v,u;
    memset(pre,-1,sizeof(pre));
    for(i=1;i<=n;++i)flow[i]=max_int;
    queue<int>que;
    pre[start]=0;
    que.push(start);
    while(!que.empty())
    {
        v=que.front();
        que.pop();
        for(i=1;i<=n;++i)
        {
            u=i;
            if(u==start||pre[u]!=-1||map[v][u]==0)continue;
            pre[u]=v;
            flow[u]=MIN(flow[v],map[v][u]);
            que.push(u);
        }
    }
    if(flow[end]==max_int)return -1;
    return flow[end];
}

但事实上并没有这么简单,上面所说的增广路还不完整,比如说下面这个网络流模型。

最大流模板【EdmondsKarp算法,简称EK算法,O(m^2n)】


我们第一次找到了1-2-3-4这条增广路,这条路上的delta值显然是1。于是我们修改后得到了下面这个流。(图中的数字是容量)

最大流模板【EdmondsKarp算法,简称EK算法,O(m^2n)】


这时候(1,2)和(3,4)边上的流量都等于容量了,我们再也找不到其他的增广路了,当前的流量是1。

但这个答案明显不是最大流,因为我们可以同时走1-2-4和1-3-4,这样可以得到流量为2的流。

那么我们刚刚的算法问题在哪里呢?问题就在于我们没有给程序一个”后悔”的机会,应该有一个不走(2-3-4)而改走(2-4)的机制。那么如何解决这个问题呢?回溯搜索吗?那么我们的效率就上升到指数级了。

而这个算法神奇的利用了一个叫做反向边的概念来解决这个问题。即每条边(I,j)都有一条反向边(j,i),反向边也同样有它的容量。

我们直接来看它是如何解决的:

 

在第一次找到增广路之后,在把路上每一段的容量减少delta的同时,也把每一段上的反方向的容量增加delta。即在Dec(c[x,y],delta)的同时,inc(c[y,x],delta)

我们来看刚才的例子,在找到1-2-3-4这条增广路之后,把容量修改成如下

最大流模板【EdmondsKarp算法,简称EK算法,O(m^2n)】


这时再找增广路的时候,就会找到1-3-2-4这条可增广量,即delta值为1的可增广路。将这条路增广之后,得到了最大流2。

最大流模板【EdmondsKarp算法,简称EK算法,O(m^2n)】

那么,这么做为什么会是对的呢?我来通俗的解释一下吧。

事实上,当我们第二次的增广路走3-2这条反向边的时候,就相当于把2-3这条正向边已经是用了的流量给”退”了回去,不走2-3这条路,而改走从2点出发的其他的路也就是2-4。(有人问如果这里没有2-4怎么办,这时假如没有2-4这条路的话,最终这条增广路也不会存在,因为他根本不能走到汇点)同时本来在3-4上的流量由1-3-4这条路来”接管”。而最终2-3这条路正向流量1,反向流量1,等于没有流量。

这就是这个算法的精华部分,利用反向边,使程序有了一个后悔和改正的机会。而这个算法和我刚才给出的代码相比只多了一句话而已。
#include<iostream>
#include<queue>
using namespace std;
const int maxn=205;
const int inf=0x7fffffff;

int r[maxn][maxn]; //残留网络,初始化为原图
bool visit[maxn];
int pre[maxn];
int m,n;

bool bfs(int s,int t)  //寻找一条从s到t的增广路,若找到返回true
{
    int p;
    queue<int > q;
    memset(pre,-1,sizeof(pre));
    memset(visit,false,sizeof(visit));

    pre[s]=s;
    visit[s]=true;
    q.push(s);
    while(!q.empty())
    {
        p=q.front();
        q.pop();
        for(int i=1;i<=n;i++)
        {
            if(r[p][i]>0&&!visit[i])
            {
                pre[i]=p;
                visit[i]=true;
                if(i==t) return true;
                q.push(i);
            }
        }
    }
    return false;
}

int EdmondsKarp(int s,int t)
{
   int flow=0,d,i;
   while(bfs(s,t))
   {
       d=inf;
       for(i=t;i!=s;i=pre[i])
           d=d<r[pre[i]][i]? d:r[pre[i]][i];
       for(i=t;i!=s;i=pre[i])
       {
           r[pre[i]][i]-=d;
           r[i][pre[i]]+=d;
       }
       flow+=d;
   }
   return flow;
}


int main()
{
    while(scanf("%d%d",&m,&n)!=EOF)
    {
        int u,v,w;
        memset(r,0,sizeof(r));///
        for(int i=0;i<m;i++)
        {
            scanf("%d%d%d",&u,&v,&w);
            r[u][v]+=w;
        }
        printf("%d\n",EdmondsKarp(1,n));
    }
    return 0;
}

<think>好的,我现在需要帮助用户了解网络流EK算法的实现和应用。首先,我要回顾一下用户的问题,确保自己理解正确。用户提到想了解EK算法的实现和应用,并且之前的引用材料中提到了相关的信息,比如EK算法的核心思想、步骤,以及一些应用场景,比如医生值班问题、资源分配等。 首先,我应该从算法的基础开始。EK算法是Edmonds-Karp的缩写,属于Ford-Fulkerson方法的一种实现,通过BFS寻找增广路径,保证了多项式时间复杂度。需要解释清楚算法的基本步骤,比如如何用BFS找最短增广路,更新残留网络,反向边的作用等。 接下来是实现的细节。用户可能需要代码示例,但根据引用5中的代码结构,可以整理出算法的框架,包括BFS部分和更新最大流的部分。需要确保代码结构清晰,可能用伪代码或某种编程语言的示例,比如Python或C++,但用户之前的问题中有Python示例,可能更适合用Python。不过要注意,最大流的实现通常涉及图的结构,比如邻接表,以及处理反向边的方式。 然后是应用部分。引用1提到了资源分配、经济学、生物学等,引用3提到了医生值班问题。需要具体说明这些应用场景是如何转化为最大流问题的,比如如何构建网络图,确定源点、汇点,边的容量等。例如,医生值班问题中,可能将医生和日期作为节点,通过边连接表示可用性,并设定合适的容量来满足需求。 还需要考虑用户可能的疑问,比如为什么使用BFS而不是DFS,时间复杂度如何,以及EK算法的优缺点。比如,BFS保证了找到的是最短增广路径,从而时间复杂度为O(VE²),相比DFS更高效,避免陷入长时间循环。 另外,引用2提到残留网络和增广路的重要性,需要解释残留网络的概念,以及算法如何通过不断更新残留网络来寻找最大流。同时,最小割最大流定理是算法正确性的基础,这一点也需要简要说明。 在组织回答时,应该分点清晰,先讲实现步骤,再讲应用案例,最后可能涉及优化或其他算法的比较,但用户主要关注EK本身,所以重点放在实现和应用上。同时,根据用户提供的引用,需要适当引用相关文献,比如引用1、3、5来支持应用场景和算法步骤的描述。 最后,生成相关问题需要考虑用户可能进一步想了解的内容,比如与其他算法的比较,复杂度分析,或者更复杂的应用案例。确保问题具有引导性,帮助用户深入学习。 现在需要确保回答的结构符合用户的要求:结构清晰,分步骤说明,包含实现代码和应用案例,引用正确,并且使用中文。同时,检查LaTeX格式是否正确,数学表达式使用$...$,独立公式用$$...$$,但根据用户提供的示例,可能不需要太多复杂公式,所以可能需要用行内公式,比如流量增加量$Δf$等。 总结,回答应包括: 1. EK算法基本原理和步骤 2. 实现细节,代码框架或伪代码 3. 应用案例,如医生值班问题 4. 算法特性(时间复杂度和优缺点) 5. 相关问题 需要避免过于技术化的术语,保持解释的易懂性,同时确保引用正确,如引用3提到的医生值班问题,引用5的代码结构,引用1的应用领域。</think>### 网络流EK算法实现与应用 #### 一、EK算法原理与实现 EK算法(Edmonds-Karp算法)是Ford-Fulkerson方法的一种高效实现,**核心是通过BFS寻找最短增广路径**,保证时间复杂度为$O(VE^2)$,避免了DFS可能陷入的指数级时间问题[^2][^5]。 ##### 实现步骤: 1. **初始化残留网络** 残留网络初始为原图,每条边$u→v$的容量为$c(u,v)$,反向边$v→u$容量为0。 2. **BFS寻找增广路径** 从源点$s$出发,用BFS找到一条到汇点$t$的路径,路径上所有边残留容量$>0$。若找不到路径,算法终止。 3. **计算流量增量$Δf$** $$Δf = \min\{ c_f(u,v) \mid (u,v) \text{在增广路径上} \}$$ 4. **更新残留网络** - 正向边容量减少$Δf$,反向边容量增加$Δf$(反向边用于“撤销”流量)[^4][^5]。 - 总流量增加$Δf$。 5. **重复步骤2-4**,直到无增广路径。 ##### 代码框架(Python伪代码): ```python def edmonds_karp(graph, s, t): max_flow = 0 parent = [-1] * len(graph) while bfs(graph, s, t, parent): # BFS找增广路径 path_flow = float('inf') v = t while v != s: # 计算最小残留容量 u = parent[v] path_flow = min(path_flow, graph[u][v]) v = u v = t while v != s: # 更新残留网络 u = parent[v] graph[u][v] -= path_flow graph[v][u] += path_flow # 反向边 v = u max_flow += path_flow return max_flow ``` #### 二、应用场景与建模方法 ##### 1. 医生值班问题 **问题描述**:医院需安排医生在$n$天值班,每天至少$k$名医生在岗,且每名医生连续工作不超过$m$天。 **建模方法**: - **构建网络图**: - 源点$s$连接所有医生节点,边容量为$m$(限制连续工作天数)。 - 医生节点连接其可工作的日期节点,边容量为1。 - 所有日期节点连接汇点$t$,边容量为$k$(每天最少需求)[^3]。 ##### 2. 资源分配问题 **问题描述**:将有限资源分配给多个任务,每个任务有最小资源需求和优先级。 **建模方法**: - 源点表示资源池,任务节点通过边连接汇点,边容量为任务需求。 - 通过中间节点限制资源冲突(如共享设备)。 ##### 3. 交通流量优化 **问题描述**:计算城市路网中从A到B的最大车流量。 **建模方法**: - 将路口作为节点,道路为有向边,边容量为车道通行能力。 - 使用EK算法求解最大流,即为路网最大通行量[^1]。 #### 三、算法特性 | 特性 | 说明 | |----------------|----------------------------------------------------------------------| | 时间复杂度 | $O(VE^2)$,适合中小规模网络[^5] | | 空间复杂度 | $O(V^2)$(邻接矩阵)或$O(V+E)$(邻接表) | | 优势 | 实现简单,BFS避免死循环 | | 局限性 | 大规模网络效率低,可改用Dinic算法(时间复杂度$O(V^2E)$)[^1][^5] | #### 四、与其他算法的对比 | 算法 | 核心思想 | 时间复杂度 | 适用场景 | |---------------|------------------------|---------------|------------------------| | Ford-Fulkerson| DFS随机找增广路 | 无保证 | 理论分析 | | **EK算法** | **BFS找最短增广路** | $O(VE^2)$ | 中小规模网络 | | Dinic算法 | 分层网络+阻塞流 | $O(V^2E)$ | 大规模稀疏图 |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值