最大流:压入与重标记算法

转自:http://hi.baidu.com/hjss06/item/7b5d353dbdeb02617c034bc5

整合一下。自己感觉该算法比ford-fulkerson更易编。

与Ford-Fulkerson方法不同,压入和重标记算法不是检查整个残留网络来找出增广路径,而是每次仅对一个顶点进行操作,并且仅检查残留网络中该顶点的相邻顶点。压入和重标记算法引入了一个新的概念叫做余流,余流的定义为e(u)=f(V,u)。我们知道,在流网络满足三个限制条件的情况下有e(u)=0,但是在该算法的执行过程中,并不能保证流守恒,但是却保持了一个“前置流”,前置流满足反对称性、容量限制、和放宽条件的流守恒特性,而这个放宽条件的流守恒特性就是指e(u)>=0,当e(u)>0时,则称顶点u溢出。下面对压入和重标记算法给出一个更直观的理解。

      继续把流网络中的边看成是运输的管道,与之前Ford-Fulkerson思想有所不同的是,这里我们将每个顶点看成是一个水库,此时,上面所讲的余流实际上就是某一个水库中的蓄水量。为了算出最终的最大流,我们是不允许水库中有余流的,所以就要将存在余流的水库中的水向其他能聚集液体的任意大水库排放,这个操作就是压入操作。而压入的方式则取决于顶点的高度,顶点的高度是一个比较抽象的概念,我们可以认为当余流从水库u压入其他水库及以后的过程中,水库u的高度随之增加,我们仅仅把流往下压,即从较高顶点压入向较低顶点压,这样就不会出现刚把一个流从u压入v后马上又被往回压的死循环的情况了。源点的高度固定为|V|,汇点的高度固定为0,所有其他顶点的高度开始时都是0,并逐步增加。算法首先从源点输送尽可能多的流出去,也就是恰好填满从源点出发每条管道,当流进入一个中间顶点时,它就聚集在该顶点的水库中,并且最终将从这里继续向下压入。

      在压入的过程中可能会发生下列情况,离开顶点u且未被填满的管道所连接的顶点的高度与u相等或者高于u,根据我们的压入规则,这时候是没有办法继续往下压入的。为了使溢出顶点u摆脱其余流,就必须增加它的高度,即称为重标记操作。我们把u的高度增加到比其最低的相邻顶点(未被填满的管道所连接的顶点)的高度高一个单位。显然,当一个顶点被重标记后,至少存在着一条管道可以排除更多的流。

      最终,有可能到达汇点的所有流均到达汇点,为了使前置流称为合法的流,根据算法的执行过程,算法会继续讲顶点的高度标记高于源点的固定高度|V|,以便把所有顶点中的余流送回源点。当除去源点和汇点的所有水库的水均为空时,我们就得到了最大流了。

(真正能想读懂的朋友最好还是要参阅《算法导论》)

附上自己写的代码(应该没问题,用过好几次了)

#include<fstream>
using namespace std;
ifstream cin("netflow.in");
ofstream cout("netflow.out");
const int Max=1001;
int edges[Max][Max],f[Max][Max],c[Max][Max],e[Max],h[Max],vertices;
bool visited[Max]={false};
int push_relable(int s,int t)
{
//Initialize
int queue[10*Max],head=0,tail=0;//队列可以用循环队列
queue[0]=s;visited[s]=true;
for(int i=1;i<=edges[s][0];i++)
{
   int u=edges[s][i];
   f[s][u]=c[s][u];
   f[u][s]=-c[s][u];
   e[s]-=c[s][u];
   e[u]=c[s][u];
   visited[u]=true;
   queue[++tail]=u;
}
h[s]=vertices;

for(;head<=tail;head++)
{
   int u=queue[head];
   visited[u]=false;
   //push
   for(int i=1;i<=edges[u][0];i++)
   {
    int v=edges[u][i];int resi=c[u][v]-f[u][v];
    if(resi>0&&h[u]==h[v]+1)
    {
     int Min=min(resi,e[u]);
     f[u][v]+=Min;
     f[v][u]=-f[u][v];
     e[u]-=Min;
     e[v]+=Min;
     if(v!=s&&v!=t&&!visited[v])
     {
      visited[v]=true;
      queue[++tail]=v;
     }
    }
   }
   //relable
   if(e[u]>0&&u!=s&&u!=t)
   {
    visited[u]=true;
    queue[++tail]=u;
    int Min=(1<<30);
    for(int i=1;i<=edges[u][0];i++)
    {
     int v=edges[u][i];int resi=c[u][v]-f[u][v];
     if(resi>0&&h[v]<Min) Min=h[v];
    }
    h[u]=max(h[u],Min+1);
   }
}

//compute netflow
int flow=0;
for(int i=1;i<=edges[s][0];i++)
   flow+=f[s][edges[s][i]];
return flow;
}
int main()
{
cin>>vertices;
int P;cin>>P;
for(int i=1;i<=P;i++)
{
   int a,b;
   cin>>a>>b;//必须记录反向边:正向边和反向边都有可能在残留图中。
   edges[a][++edges[a][0]]=b;
   edges[b][++edges[b][0]]=a;
   c[a][b]=1;
   c[b][a]=0;
}
cout<<push_relable(1,vertices)<<endl;
return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值