Push-Relabel算法

http://blog.sina.com.cn/s/blog_60a0e97e0101bfj9.html


Push-Relabel算法
        直观思想:先加入充足的流(跟s相连的所有边的容量之和),加入之后呢,再慢慢一个边一个边的向汇点渗透。直到没法再渗透(类似于ford-fulkerson算法中找不到增广路径了),那么这时再把一些剩余的流回收到source就可。
        主要分为两个步骤:push和relabel。push表示从任一个节点找出次节点的存水量和另一个相邻节点的剩余流量,比较哪一个小,借此可得此节点在增加该路径的流量之后是否还有存水量。要实现该push的操作必须满足三个条件:该点存水量>0,管线的容量大于流量,该点高度大于另一个点高度。relabel表示某一个节点存水量大于0但水流不出去时,重新设置该节点高度,使得该节点的存水量流入比它低的节点。

算法步骤
  1. 初始化前置流:将与源点s相连的管道流量f(0,i)设为该管道的容量,即 f(0,i)=c(0,i);将源点s的高度h(0)=V,(V表示图的顶点个数),其余顶点高度h(i)=0;将源的点余量e(0)设为源容量减去源的流出量,即e(0)=-∑f(0,i)=-∑c(0,i),与源s相连的点余量设为该点的流入量e(i)=c(0,i),其余点都为0。
  2. 构造一个存储顶点的队列vlist,用以检查点的压栈。从源点s出发,将与之相连的顶点压入栈。
  3. 每次从栈中取首个元素,即某一个点,检查其点余量e(i),若不为0,表示要对该点进行操作——重标记或者压入流:检查与该点i全部的相邻点j,若该点比它相邻点的高度大h(i)>h(j)且该管道的容量c(i,j)大于流量f(i,j)时,将该点的余量以最大方式压入该管道delta=min(e(i), c(i,j)-f(i,j)), 点余量e、流量f相应的进行减加,另外在队列中加入满足点余量e(j)>0的相邻点j(vlist.push(j); j原不存在该队列中);若没有相邻点满足上述条件,则将该点的高度值h(i)根据相邻点j进行增加,h(i)=min(h(j))+1。以上的重标记或压入流操作循环进行,直至该点的余量e(i)为0。
  4. 重复第3步,直至队列vlist中没有元素,停止算法,最后输出汇点t的余量e(t),t=V-1, 该值就是最后所求的最大流。最小割就是选择以上
扩展
  • Push-Relabel算法中的顶点i的最大增加量为2V-1,(数学上可以证明),因此到最后可能与源相邻的某个点i会以高度h(i)>V的方式将多余量返回给源(源的高度值始终是V);
  • Push-Relabel算法的复杂度为O(V2E)


添加一个示例代码:


http://www.cppblog.com/Icyflame/archive/2009/06/24/88448.html


<pre name="code" class="cpp">#include<iostream>
using namespace std;
#define MAXN 202
int s, t;
int n, np, nc, m;
char str[50];
int c[MAXN][MAXN];
int f[MAXN][MAXN];
int e[MAXN];
int h[MAXN];
void push(int u, int v)
{
    int d = min(e[u], c[u][v] - f[u][v]);
    f[u][v] += d;
    f[v][u] = -f[u][v];
    e[u] -= d;
    e[v] += d;
}
bool relabel(int u)
{
    int mh = INT_MAX;
    for(int i=0; i<n+2; i++)
    {
        if(c[u][i] > f[u][i])
            mh = min(mh, h[i]);
    }
    if(mh == INT_MAX)
        return false; //残留网络中无从u出发的路
    h[u] = mh + 1;
    for(int i=0; i<n+2; i++)
    {
        if(e[u] == 0) //已无余流,不需再次push
            break;
        if(h[i] == mh && c[u][i] > f[u][i]) //push的条件
            push(u, i);
    }
    return true;
}
void init_preflow()
{
    memset(h, 0, sizeof(h));
    memset(e, 0, sizeof(e));
    h[s] = n+2;
    for(int i=0; i<n+2; i++)
    {
        if(c[s][i] == 0)
            continue;
        f[s][i] = c[s][i];
        f[i][s] = -f[s][i];
        e[i] = c[s][i];
        e[s] -= c[s][i];
    }
}
void push_relabel()
{
    init_preflow();
    bool flag = true; //表示是否还有relabel操作
    while(flag)
    {
        flag = false;
        for(int i=0; i<n; i++)
            if(e[i] > 0)
                flag = flag || relabel(i);
    }
}
int main()
{
    while(scanf("%d%d%d%d", &n, &np, &nc, &m) != EOF)
    {
        s = n; t = n+1;
        memset(c, 0, sizeof(c));
        memset(f, 0, sizeof(f));
        while(m--)
        {
            scanf("%s", &str);
            int u=0, v=0, z=0;
            sscanf(str, "(%d,%d)%d", &u, &v, &z);
            c[u][v] = z;
        }
        for(int i=0; i<np+nc; i++)
        {
            scanf("%s", &str);
            int u=0, z=0;
            sscanf(str, "(%d)%d", &u, &z);
            if(i < np)
                c[s][u] = z;
            else if(i >= np && i < np + nc)
                c[u][t] = z;
        }
        push_relabel();
        printf("%d\n", e[t]);
    }
}


 

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值