一、问题描述
在下图的网络中,有一个源点机器 s s s有一个汇点机器 t t t,边的权值表示电缆的传输速度。我们的目标使求解源点 s s s到汇点 t t t的最大流量。
二、解题思路
贪心算法:
- 看到这个问题;我们最简单的第一反应就是使用DFS的贪心算法;算法步骤:
- 第一步:使用DFS搜索一条从源点 s s s到汇点 t t t的可以增流的路径(即路径中边的容量 c ( e ) > f ( e ) c(e)>f(e) c(e)>f(e));并记录下该路径上最多能增多少流(即路径上所有边的( c ( e ) − f ( e ) c(e)-f(e) c(e)−f(e))的最小值)。
- 第二步:进行增流,即修改路径上所有边的流量值 f ( e ) f(e) f(e)。
- 第三步:重复第一步和第二步,直到找不到可以增流的路径为止。
- 当然了这种贪心算法肯定是错误的,现在我们来看一下这种算法错在哪里;假设我们对上图的网络进行实行这种贪心算法:
Ford-Fulkerson算法:
- 为了贪心算法的问题,我们对原图上的每条边增加一条反向的边,该条反向的边的容量 c ′ ( e ) c'(e) c′(e)就等于该条边的流量 f ( e ) f(e) f(e) .
- 反向边的用途(作者个人的一个理解):悔棋;我们进行贪心算法时每次都对路径做最大的增流,但是这种做法又是不是最优的,所有当贪心算法进行当前步骤时如果发现以前的步骤增流过多时反向边允许我们悔棋,即允许我们对以前的增加的流量进行撤销。
- 经过贪心算法得到的流量的10的最大流,如上图所示,现在我们增加反向边,如下图所示再增流:
- Ford-Fulkerson算法步骤:
- 第一步:对原图的每条边增加一条反向边,原来边的权值为边的 c ( e ) − f ( e ) c(e)-f(e) c(e)−f(e),反向边的权值 f ( e ) f(e) f(e);初始时 f ( e ) = 0 f(e)=0 f(e)=0
- 第二步:按照贪心算法进行增流。
- Ford-Fulkerson算法复杂度: O ( F ∣ E ∣ ) O(F|E|) O(F∣E∣)其中 F F F为最大流。
代码:
#include <iostream>
#include <vector>
#include<cstring>
using namespace std;
#define Max 1002
#define INF 999999
struct edge//边集结构体
{
int to;//弧尾下标
int cap;//边的权值
int rev;//对应反向边的下标
};
vector<edge> G[Max];//图的邻接表
bool used[Max];
void add_adge(int from,int to,int cap)//向图中填加一条边及其反向边
{
G[from].push_back((edge){to,cap,G[to].size()}) ;
G[to].push_back((edge){from,0,G[from].size()-1}) ;
}
//DFS搜索可以增流的路径
int DFS(int v,int t,int f)
{//v:当前点;t:汇点;f:路径能增加的最大流量
if(v==t)
return f;
used[v]=true;
for(int i=0;i<G[v].size();i++)
{
edge& e=G[v][i];
if(!used[e.to]&&e.cap>0)
{
int d=DFS(e.to,t,min(f,e.cap));
if(d>0)
{
e.cap-=d;
G[e.to][e.rev].cap+=d;
return d;//每次DFS只更新一条路径
}
}
}
return 0;
}
int main()
{
int N,M;//节点数和边数
cin>>N>>M;
int from,to,cap;
for(int i=0;i<M;i++)
{
cin>>from>>to>>cap;
add_adge(from,to,cap);
}
int s,t;
cin>>s>>t;
int flow=0;
while(true)
{
memset(used,0,sizeof(used));
int f=DFS(s,t,INF);
if(f==0)
break;
flow+=f;
}
cout<<"最大流量为:"<<flow<<endl;
return 0;
}
测试样例:
5 7
0 1 10
0 2 2
1 2 6
1 3 6
2 4 5
3 2 3
3 4 8
0 4
输出结果:
最大流量为:11