对最大流问题比较感性的认识,要看证明还是要看算法导论的相关章节。
最大流问题:
给定一个有向图,一般情况下边的值为整数,定义不直接相连的节点间的边值为0,如果有节点i和j直接由多条边,则将这些边合并为一条,值取和。则若i到j有边,则j到i的边为0,这些边称为反向边。定义其中的两个点位源点和汇点,则这个有向图可以视为流网络。网络中的有向边的值表示可以通过该边的最大流量,最大流问题就是求从源点出发,流进汇点的最大流量。
为了求最大流,必须了解残余网络,增广路径,反向弧等概念。
残余网络是针对流网络的某个状态而定义的。
如图是一个流网络的状态,其中1为源点,7为汇点,每天边的值为v/c的形式表示,v代表当前状态该边上的实际流量,c代表该边的最大流量:
则它的残余网络就是从中找到一个从源点到汇点的路径(该路径中不存在值为0的边),选取这条路径中v的最小值minflow,也就是该路径的实际流量,然后将该路径的所有边的实际容量都更新为c(i,j)-minflow,并修改其反向边的值为c(j,i)+minflow(这一步的意义可能是直接的贪心求法无法得到最优解,需要增加方向边来修正错误?)。这样的一条路径称为增广路径,修改值后的流网络成为残留网络。增广路径的意义在于能给当前最大流增加多少的值,而残余网络的意义在于每个节点从源点的流入量还能增加多少,0表示不能再增加了。当残留网络中没有增光路经时,则当前流入汇点的流值为最大流。
下图是找到两条增光路经,并修改流网络后的残余网络(我感觉此时3到4,5到6和6到7之间应该仍然为0):
以上方法为Ford-Fulkerson方法,该方法的一种实现是基于BFS的Edmonds-Karp算法。
算法流程如下:
设队列Q:存储当前未访问的节点,队首节点出队后,成为已检查的标点;
Path数组:存储当前已访问过的节点的增广路径;
Flow数组:存储一次BFS遍历之后流的可改进量;
Repeat:
Path清空;
源点S进入Path和Q,Path[S]<-0,Flow[S]<-+∞;
While Q非空 and 汇点T未访问 do
Begin
队首顶点u出对;
For每一条从u出发的弧(u,v) do
If v未访问 and 弧(u,v) 的流量可改进;
Then Flow[v]<-min(Flow[u],c[u][v]) and v入队 and Path[v]<-u;
End while
If(汇点T已访问)
Then 从汇点T沿着Path构造残余网络;
Until 汇点T未被访问
设队列Q:存储当前未访问的节点,队首节点出队后,成为已检查的标点;
Path数组:存储当前已访问过的节点的增广路径;
Flow数组:存储一次BFS遍历之后流的可改进量;
Repeat:
Path清空;
源点S进入Path和Q,Path[S]<-0,Flow[S]<-+∞;
While Q非空 and 汇点T未访问 do
Begin
队首顶点u出对;
For每一条从u出发的弧(u,v) do
If v未访问 and 弧(u,v) 的流量可改进;
Then Flow[v]<-min(Flow[u],c[u][v]) and v入队 and Path[v]<-u;
End while
If(汇点T已访问)
Then 从汇点T沿着Path构造残余网络;
Until 汇点T未被访问
POJ上一到入门级的最大流问题:例题:
http://poj.org/problem?id=1273
例题的代码实现,基本仿照参考链接:
说明:
start:源点
terminal:汇点
map[LEN][LEN]:流网络
bfsqueue:每一状态中,用BFS最增广路经,每次只求一条增广路径
minflow[LEN]:记录从源点流入每个点的最大可能值,这个值也将该路径中的最小值(该路径的实际流量)一直传递到汇点。该值是每次BFS都要保留的
path[LEN]:记录增广路径,其中path[j] = i表示从节点 i 流向节点 j 。同时也记录了每次bfs时记录是否考察了当前节点。
每次BFS完后,查看path[terminal],如果path[terminal]==-1,则表示没有从源点到汇点的增广路径了。
每次BFS完后,要更新增广路径中的容量和方向路径的容量,因为minflow已经记录了增光路经中的最小值,用minflow[terminal]更新即可。
#define LEN 201 int map[LEN][LEN]; int minflow[LEN]; int n,m; int start; int terminal; queue< int> bfsqueue; int bfs(int path[LEN]){ memset(path,-1, sizeof (int )*LEN); while (!bfsqueue.empty()) bfsqueue.pop(); path[start] = 0; minflow[start] = MAXINT; bfsqueue.push(start); while (!bfsqueue.empty()){ int node = bfsqueue.front(); bfsqueue.pop(); for (int i=1;i<=m;++i){ if (path[i] == -1 && map[node][i]!=0 && i != start){ minflow[i] = (minflow[node]<map[node][i])?minflow[node]:map[node][i]; path[i] = node; bfsqueue.push(i); } } } if (path[terminal] == -1) return -1; return minflow[terminal]; } int edmonds_karp(){ int max_flow = 0; int path[LEN]; int increasedflow; int node; while ( (increasedflow=bfs(path)) != -1){ max_flow += increasedflow; node = terminal; while (node != start){ int pre = path[node]; map[pre][node] -= increasedflow; map[node][pre] += increasedflow; node = pre; } } return max_flow; }