题目:利用Ford_Fulkerson (标号法)求图1(a)及2(a)所示的容量网络的最大流,输出各条弧及其流量,以及求得的最大流流量。
(1)
(2)
分析:
在下面的程序中,以邻接矩阵存储容量网络,但邻接矩阵中的元素为结构体ArcType类型变量。该结构体描述了网络中弧的结构,包含容量c和流量f两个成员。在程序中,还定义了三个数组:flag[n], prev[n], alpha[n],其中:
1) flag[n]表示顶点状态,其元素取值及含义为:-1-未标号,0-已标号未检查,1-已标号已检查;
2) prev[n]为标号的第一个分量:指明标号从哪个顶点得到,以便找出可改进量;
3) alpha[n]为标号的第二个分量:用以确定增广路的可改进量α。
另外,如前所述,从一个已标号未检查的顶点出发,对它的邻接顶点进行标号时,采用的是广度优先搜索的策略,因此,在程序中,定义了一个数组queue[n]来模拟队列;并定义了两个相关变量qs和qe,分别表示队列头位置和队列尾位置,约定从队列头取出结点,从队列尾插入结点;当qs<qe时表示队列非空。
每一次标号过程为:
(1) 先将flag、prev和alpha这3个数组各元素都初始化-1。
(2) 将源点初始化为已标号未检查顶点,即flag[0] = 0, prev[0] = 0, alpha[0] = INF,INF表示无穷大;并将源点入队列。
(3) 当队列非空并且汇点没有标号,从队列头取出队列头顶点,设这个顶点为v,v肯定是已标号未检查顶点;因此,检查顶点v的正向和反向“邻接”顶点,如果没有标号并当前可以进行标号,则对这些顶点进行标号并入队列,这些顶点都是已标号未检查顶点;此后顶点v为已标号已检查顶点。反复执行这一步直至队列为空或汇点已获得标号。
标号完毕后,要进行调整,调整方法是:从汇点出发,通过标号的第1个分量,即prev[5],采用“倒向追踪”方法,一直找到源点为止,这个过程途经的顶点和弧就构成了增广路。可改进量为汇点标号的第2个分量,即alpha[5]。
代码:
#include <iostream>
#include <cstring>
#include <cmath>
#include <cstring>
using namespace std;
#define MIN(a, b) (a > b ? b : a)
const int MAXN = 1000;
const int INF = 10000000;
struct ArcType//每个弧的容量以及弧的实际流量
{
int c, f;
};
ArcType Edge[MAXN][MAXN];
int flag[MAXN];//如果该顶点没有标号则为-1,如果已经访问,且没有检查其邻接顶点则标记为0,如果已经访问并且已经检查其邻接顶点则标记为1;
int pre[MAXN];//pre[i]表示标号时,顶点i的前一个顶点,即第一个分量
int alpha[MAXN];//标号时第二个分量
int que[MAXN];//广搜时的队列
int v;//队列头元素出队时,接收队头元素;
int qs, qe;//qs类似队列对头指针,qe为队尾指针
int i, j;
int n, m;//图的顶点数,边数
void Ford_Fulkerson()
{
while(1)
{
memset(flag, 0xff, sizeof(flag));
memset(alpha, 0xff, sizeof(alpha));
memset(pre, 0xff, sizeof(pre));
flag[0] = 0;//源点标记已经访问而未检查,并且入队,
pre[0] = 0;//一般都是把源点的前一个顶点设置为0,其第二个分量标记为无穷大,因为从源点可以流出任意多的流量
alpha[0] = INF;
qs = qe = 0;
que[qe++] = 0;
while(qs < qe && flag[n-1] == -1)//如果汇点未被标记,则进行下去
{
v = que[qs++];
for(i = 0; i < n; ++i)//检查v的邻接顶点
{
if(flag[i] == -1)//顶点i未被访问
{
if(Edge[v][i].c < INF && Edge[v][i].f < Edge[v][i].c)
{
pre[i] = v;
flag[i] = 0;
alpha[i] = MIN(alpha[v], Edge[v][i].c - Edge[v][i].f);
que[qe++] = i;
}
else if( Edge[i][v].c < INF && Edge[i][v].f > 0)
{
pre[i] = -v;
flag[i] = 0;
alpha[i] = MIN(alpha[v], Edge[i][v].f);
que[qe++] = i;
}
}
}
flag[v] = 1;//把v标记为已经访问,并且已经检查过了
}
if(flag[n-1] == -1 || alpha[n-1] == 0)//如果汇点不能被标记,或者被标记了但是其第二个分量为0,则表示图中已经不存在增广路,已经达到最大流
break;
int k1 = n-1, k2 = fabs( pre[k1] );//倒向追踪,如果是正向弧则该弧的实际流量加上汇点的第二分量(即可增加量),如果是反向弧,则反向弧上的实际流量减去汇点的第二个分量
int a = alpha[k1];
while( 1 )
{
if(Edge[k2][k1].f < INF)
Edge[k2][k1].f = Edge[k2][k1].f + a;
else
Edge[k1][k2].f = Edge[k1][k2].f - a;
if(k2 == 0)
break;
k1 = k2, k2 = fabs(pre[k2]);
}
}
int maxflow = 0;
for(i = 0; i < n; ++i)//计算最大流,并且输出各个弧的流量
{
for(j = 0; j < n; ++j)
{
if(i == 0 && Edge[i][j].f < INF)
maxflow += Edge[i][j].f;
if(Edge[i][j].f < INF)
cout<<i<<" -> "<<j<<":"<<Edge[i][j].f<<endl;
}
}
cout<<"MAXFLOW: "<<maxflow<<endl;
}
int main()
{
int u, v, c, f;
while(cin>>n>>m)//n是顶点数,m是边数
{
if(n == 0 && m == 0)
break;
for(i = 0; i < n; ++i)
for(j = 0; j < n; ++j)
Edge[i][j].c = Edge[i][j].f = INF;
for(j = 0; j < m ; ++j)
{
cin>>u>>v>>c>>f;
Edge[u][v].c = c;
Edge[u][v].f = f;
}
Ford_Fulkerson();
}
return 0;
}
/**
6 10
0 1 8 2
0 2 4 3
1 3 2 2
1 4 2 2
2 1 4 2
2 3 1 1
2 4 4 0
3 4 6 0
3 5 9 3
4 5 7 2
*/