网络流 练习

题目:利用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
*/



 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值