最大流算法总结

Edmonds-Karp:

http://blog.csdn.net/moon_1st/article/details/5305524

这个网站上讲解了一些概念可以了解一下。

Edmonds-Karp算法是Ford-Fulkerson方法的bfs实现,每次找增广路,把这条路上的所有点的流量加上这条路上的残余容量,再找新的增广路,直到找不到为止

在无权边的有向图中寻找最短路,最简单的方法就是广度优先搜索 (BFS),E-K 算法就直接来源于此。每次用一遍 BFS 寻找从源点 s 到终点 t 的最短路作为增广路径,然后增广流量 f 并修改残量网络,直到不存在新的增广路径。

E-K 算法的时间复杂度为 O(VE2),由于 BFS 要搜索全部小于最短距离的分支路径之后才能找到终点,因此可以想象频繁的 BFS 效率是比较低的。


下面时代码,其中fa存储的是增光路径,f是记录流量的相邻矩阵,c是记录容量的矩阵,需要注意的是queue队列不要开成全局变量:

void Edmonds_Karp()
{

    int i,d;
    long long ans=0;
    int s;
    fa[m]=1;
    while(fa[m]!=0)
    {
        queue<int> zui;
        zui.push(1);
        memset(fa,0,sizeof(fa));
        fa[1]=1;
        while(!zui.empty()&&fa[m]==0)
        {
            i=zui.front();
            zui.pop();
            for(int j=1; j<=m; j++)
            {
                if(fa[j]==0)
                    if(f[i][j]<c[i][j])
                    {
                        fa[j]=i;
                        zui.push(j);
                    }
                    else if(f[j][i]>0)
                    {
                        fa[j]=-i;
                        zui.push(j);
                    }
            }
        }

        if(fa[m]!=0)
        {
            i=m;
            d=INT_MAX;
            while(i!=1)
            {
                if(fa[i]>0)
                {
                    if(c[fa[i]][i]-f[fa[i]][i]<d)
                        d=c[fa[i]][i]-f[fa[i]][i];
                }
                else if(f[i][-fa[i]]<d)
                    d=f[i][-fa[i]];
                i=abs(fa[i]);
            }
            ans+=d;
            i=m;
            while(i!=1)
            {
                if(fa[i]>0)
                    f[fa[i]][i]+=d;
                else
                    f[i][-fa[i]]-=d;
                i=abs(fa[i]);
            }
        }
    }
}

#include<cstdio>        //EK()算法。时间复杂度(VE^2)
#include<queue>
#include<cstring>
using namespace std;
const int maxn = 100;
const int INF = (1<<30)-1;
int g[maxn][maxn];
int flow[maxn],pre[maxn];
bool vis[maxn];
int n,m;
int bfs(int s,int e){
    memset(pre,-1,sizeof(pre));
    memset(vis,false,sizeof(vis));
    queue<int > q;
    vis[s] = true;
    for(int i=1;i<=n;i++)   flow[i]=INF;
    q.push(s);
    while(!q.empty()){
        int now = q.front();  q.pop();
        if(now==n)  break;
        for(int i=1;i<=n;i++){                //寻找增广路最小流量
            if(!vis[i]&&g[now][i]>0){
                vis[i] = true;
                flow[i] = min(flow[now],g[now][i]);
                pre[i] = now;
                q.push(i);
            }
        }
    }
    if(!vis[e]|| e==1)                         //找不到完整的增广路or源点汇点重合
         return -1;
    else
         return flow[e];
}
int EK(int s,int e){
    int temp,d,res,maxflow;
    maxflow = 0;
    while((d=bfs(s,e))!=-1){
         maxflow += d;
         temp=n;
         while(temp!=1){
             res = pre[temp];
             g[res][temp]-=d;               //正向边
             g[temp][res]+=d;               //反向边
             temp = res;
         }
    }
    return maxflow;
}
int main(){
    int T,ca=1;
    int start,end,capacity;
    scanf("%d",&T);
    while(T--){
        memset(g,0,sizeof(g));
        scanf("%d %d",&n,&m);
        for(int i=1;i<=m;i++){
            scanf("%d%d%d",&start,&end,&capacity);
            g[start][end]+=capacity;
        }
        printf("Case %d: %d\n",ca++,EK(1,n));
    }
    return 0;
}

const int INF=999999999;

int c[110][110],f[110][110],pre[110];
int q[110],d[110];
int n,m,s,t,res;

bool bfs()
{
    int i,j,tail=1,head=0;
    q[tail]=s;
    pre[s]=-1;
    memset(d,0,sizeof(d));
    d[s]=INF;
    while(head<tail)
    {
        i=q[++head];
        for(j=1;j<=n*2;j++)
        {
            if(c[i][j]>f[i][j]&&!d[j])
            {
                d[j]=min(d[i],c[i][j]-f[i][j]);
                pre[j]=i;
                q[++tail]=j;
                if(j==t)
                   return true;
            }
        }
    }
    return false;
}
int EK()
{
    res=0;
    while(bfs())
    {
        for(int i=t;i!=s;i=pre[i])
        {
            f[pre[i]][i]+=d[t];
            f[i][pre[i]]-=d[t];
        }
        res+=d[t];
    }
    return res;
}


Dinic算法(借鉴的别人的):

算法步骤:
Dinic算法的思想也是分阶段地在层次图中增广。它与最短路径增值算法不同之处是:在Dinic算法中,我们用一个dfs过程代替多次bfs来寻找阻塞流。下面给出其算法步骤:
  1. 初始化流量,计算出剩余图  
  2. 根据剩余图计算层次图。若汇点不在层次图内,则算法结束  
  3. 在层次图内用一次dfs过程增广  
  4. 转步骤2  
  1. 用BFS建立分层图  注意:分层图是以当前图为基础建立的,所以要重复建立分层图
  2. 用DFS的方法寻找一条由源点到汇点的路径,获得这条路径的流量I 根据这条路径修改整个图,将所经之处正向边流量减少I,反向边流量增加I,注意I是非负数
  3. 重复步骤2,直到DFS找不到新的路径时,重复步骤1

注意(可以无视):

  • Dinic(其实其他的好多)算法中寻找到增广路后要将反向边增加I
  • Dinic中DFS时只在分层图中DFS,意思是说DFS的下一个节点的Dis(距源点的距离)要比自己的Dis大1,例如在图1中第一个次DFS中,1->2->4 这条路径是不合法的,因为Dis[2]=1;Dis[4]=1;

下面是dfs的过程:
ps;
While outdegree(s)>0
    up.top;
    if u<>t
        if outdegree(u)>0
            设(u,v)为层次图中的一条边;
            pp,v;   
        else
            从p和层次图中删除点u,
            以及和u连接的所有边;
    else
        增广p(删除了p中的饱和边);
        令p.top为p中从s可到达的最后顶点;
end while

在程序里,p表示找到的增广路径,p.top为路径中的最后一个顶点。一开始,p中只有源点。

整个While循环分为2个操作。如果p的最后一个顶点为汇点,也就是说找到了增广路,那么对p增广,注意到增广后一定有一条或多条p中的边被删除了。这时,我们使增广路径后退至p中从源点可到达的最后一个顶点。

如果p的最后一个顶点不为汇点,那么观察最后那个的顶点u 。若在层次图中存在从u连出的一条边,比如(u,v),我们就将顶点v放入路径p中,继续dfs遍历;否则,点u对之后的dfs遍历就没有用了,我们将点u以及层次图中连到u的所有边删除,并且在p中后退一个点。

Dfs过程将会不断重复这2个操作,直到从源点连出的边全部被删除为止。
二、Dinic 算法模板

void Dinic()
{
    for(;;){
        BFS();
        if(D[T]==-1)break;
        int path_n=0;
        int x=S;
        memcpy(cur,E,sizeof(cur));
        for(;;){
            if(x==T){
                int mink=-1,delta=INT_MAX;
                for(int i=0;i<path_n;++i){
                    if(path[i]->c<delta){
                        delta=path[i]->c;
                        mink=i;
                    }
                }
                for(int i=0;i<path_n;++i){
                    path[i]->c-=delta;
                    path[i]->back->c+=delta;
                }
                path_n=mink;
                x=path[path_n]->x;
            }
            edge* e;
            for(e=cur[x];e;e=e->next){
                if(e->c==0)
                    continue;
                int y=e->y;
                if(D[x]+1==D[y])
                    break;
            }
            cur[x]=e;
            if(e){
                path[path_n++]=e;
                x=e->y;
            }
            else{
                if(path_n==0)
                    break;
                D[x]=-1;
                --path_n;
                x=path[path_n]->x;
            }
        }
    }
}

#include<iostream>
#define Max 210
int flow[Max][Max],d[Max]; //flow is the network
int sta,end,N; //sta is the sourse ,end is the,N is the number of vector
bool bfs(int s)
{
    int front=0,rear=0;
    int q[Max];
    memset(d,-1,sizeof(d));  //d[] is the deep
    q[rear++]=s;
    d[s]=0;
    while(front<rear)
    {
        int k=q[front++];
        for(int i=0; i<=N; i++)
            if(flow[k][i]>0&&d[i]==-1)
            {
                d[i]=d[k]+1;
                q[rear++]=i;
            }
    }
    if(d[end]>=0)   return true;
    return false;
}
int dinic(int k,int sum)  //k is the sourse
{
    if(k==end)    
        return sum;
    int os=sum;
    for(int i=0; i<=N&∑ i++)
        if(d[i]==d[k]+1&&flow[k][i]>0)
        {
            int a=dinic(i,min(sum,flow[k][i])); //Deep to the end.
            flow[k][i]-=a;
            flow[i][k]+=a;
            sum-=a;
        }
    return os-sum;
}
int main()
{
    int ret=0;
    while(bfs(sta))
        ret+=dinic(sta,INT_MAX);
    printf("%d\n",ret);
    return 0;
}


还有一个更高效的算法,据说是没有数据能卡住,SAP。

SAP算法模板:

点击打开链接

http://blog.csdn.net/lhshaoren/article/details/7795342

typedef struct node{  
       int v, w;  
       struct node *nxt, *op;  
}NODE;  
  
NODE edg[MM]; // 保存所有的边   
NODE *link[NN]; // 记录节点所在链表的首节点   
int h[NN];    // 距离标号,记录每个点到汇点的距离,这里的距离指的是层数   
int num[NN];  // gap优化,标号为i的顶点个数   
  
int M, N, idx, S, T, n; // S 表示源点,T表示汇点,n表示节点个数   
void add(int u, int v, int c){//很典型的加边法,不过是用链表存储的。  
     idx++;  
     edg[idx].v = v;  
     edg[idx].w = c;  
     edg[idx].nxt = link[u];  
     edg[idx].op = edg + idx + 1;  
     link[u] = edg + idx;       
     idx++;  
     edg[idx].v = u;  
     edg[idx].w = 0;  
     edg[idx].nxt = link[v];  
     edg[idx].op = edg + idx - 1;  
     link[v] = edg + idx;  
}  
  
int Min(int a, int b){  
    return a < b ? a : b;  
}  
  
int aug(int u, int flow){  
    if (u == T) return flow;  
    int l = flow;  // l表示剩余容量   
    int tmp = n - 1;  
    for (NODE *p = link[u]; p; p = p->nxt){//遍历u点相关联的其它点  
        if (h[u] == h[p->v] + 1 && p->w){  
           int f = aug(p->v, Min(l, p->w));  
           l -= f;  
           p->w -= f;  
           p->op->w += f;  
           if (l == 0 || h[S] == n) return flow - l; // gap  
        }  
        // 这里是有剩余容量的可行边   
        if (p->w > 0 && h[p->v] < tmp){  
           tmp = h[p->v];    
        }  
    }  
    if(l == flow){//  如果没有找到增流,才修改标号,刚开始写错了,也杯具的过了好多题   
        num[h[u]]--;   // gap   
        if (num[h[u]] == 0) h[S] = n; // gap,每个点的距离值最多为n - 1,这里设为n 表示断层了   
        else{  
           h[u] = tmp + 1;  
           num[h[u]]++;   // gap  
        }  
    }  
    return flow - l;.//这里是L不是1.  
}  
/*n表示总点的个数,包括源点和汇点*/  
void sap(){  
     int ans = 0;  
     memset(h, 0, sizeof(h)); // h 保存的是距离标号(到汇点的)   
     memset(num, 0, sizeof(num));  
     num[0] = n;  
     while(h[S] < n){  
         ans += aug(S, INF);   
     }  
     printf("%d\n", ans);  
}  

邻接表实现:

const int MAXN=20010;//点数的最大值
const int MAXM=880010;//边数的最大值
const int INF=0x3f3f3f3f;

struct Node
{
    int from,to,next;
    int cap;
}edge[MAXM];
int tol;
int head[MAXN];
int dep[MAXN];
int gap[MAXN];//gap[x]=y :说明残留网络中dep[i]==x的个数为y

int n;//n是总的点的个数,包括源点和汇点

void init()
{
    tol=0;
    memset(head,-1,sizeof(head));
}

void addedge(int u,int v,int w)
{
    edge[tol].from=u;
    edge[tol].to=v;
    edge[tol].cap=w;
    edge[tol].next=head[u];
    head[u]=tol++;
    edge[tol].from=v;
    edge[tol].to=u;
    edge[tol].cap=0;
    edge[tol].next=head[v];
    head[v]=tol++;
}
void BFS(int start,int end)
{
    memset(dep,-1,sizeof(dep));
    memset(gap,0,sizeof(gap));
    gap[0]=1;
    int que[MAXN];
    int front,rear;
    front=rear=0;
    dep[end]=0;
    que[rear++]=end;
    while(front!=rear)
    {
        int u=que[front++];
        if(front==MAXN)front=0;
        for(int i=head[u];i!=-1;i=edge[i].next)
        {
            int v=edge[i].to;
            if(dep[v]!=-1)continue;
            que[rear++]=v;
            if(rear==MAXN)rear=0;
            dep[v]=dep[u]+1;
            ++gap[dep[v]];
        }
    }
}
int SAP(int start,int end)
{
    int res=0;
    BFS(start,end);
    int cur[MAXN];
    int S[MAXN];
    int top=0;
    memcpy(cur,head,sizeof(head));
    int u=start;
    int i;
    while(dep[start]<n)
    {
        if(u==end)
        {
            int temp=INF;
            int inser;
            for(i=0;i<top;i++)
               if(temp>edge[S[i]].cap)
               {
                   temp=edge[S[i]].cap;
                   inser=i;
               }
            for(i=0;i<top;i++)
            {
                edge[S[i]].cap-=temp;
                edge[S[i]^1].cap+=temp;
            }
            res+=temp;
            top=inser;
            u=edge[S[top]].from;
        }
        if(u!=end&&gap[dep[u]-1]==0)//出现断层,无增广路
          break;
        for(i=cur[u];i!=-1;i=edge[i].next)
           if(edge[i].cap!=0&&dep[u]==dep[edge[i].to]+1)
             break;
        if(i!=-1)
        {
            cur[u]=i;
            S[top++]=i;
            u=edge[i].to;
        }
        else
        {
            int min=n;
            for(i=head[u];i!=-1;i=edge[i].next)
            {
                if(edge[i].cap==0)continue;
                if(min>dep[edge[i].to])
                {
                    min=dep[edge[i].to];
                    cur[u]=i;
                }
            }
            --gap[dep[u]];
            dep[u]=min+1;
            ++gap[dep[u]];
            if(u!=start)u=edge[S[--top]].from;
        }
    }
    return res;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值