【图论】最大流之EK算法与Dinic算法及最小费用最大流

最大流:

给出一张网络图,并指定源点和终点,每条边都有它的容量,起点有着无限的流量,求从源点到经过的所有路径的最终到达汇点的最大流量和。对于同一个节点,流入的流量之和和流出的流量之和相同,即假如结点1有12流量流入结点2,结点2分别有8流量流入结点3,4流量流入结点4,这种情况是可以的。 


EK算法:

而EK算法反复寻找源点s到汇点t之间的增广路径,若有,找出增广路径上每一段[容量-流量]的最小值delta,若无,则结束。 并且更新残留网络的值(涉及到反向边)。所有的正向边减去delta,所有的反向边加上delta.由于反向边存在容量,所以下次寻找增广路径可以走该反向边,这种设计使得我们可以抵消之前的操作,找到更为适合的使总流量增加的边,使程序有了一个后悔和改正的机会。找到delta后,则使最大流值加上delta,更新为当前的最大流值。

int maxData = 0x7fffffff;
int capacity[arraysize][arraysize]; //记录残留网络的容量
int flow[arraysize];                //标记从源点到当前节点实际还剩多少流量可用
int pre[arraysize];                 //标记在这条路径上当前节点的前驱,同时标记该节点是否在队列中
int n,m;
queue myqueue;
int BFS(int src,int des)
{
    int i,j;
    while(!myqueue.empty())       //队列清空
        myqueue.pop();
    for(i=1;i0 && pre[i]==-1)
            {
                 pre[i] = index; //记录前驱
                 flow[i] = min(capacity[index][i],flow[index]);   //关键:迭代的找到增量
                 myqueue.push(i);
            }
        }
    }
    if(pre[des]==-1)      //残留图中不再存在增广路径
        return -1;
    else
        return flow[des];
}
int maxFlow(int src,int des)
{
    int increasement= 0;
    int sumflow = 0;
    while((increasement=BFS(src,des))!=-1)
    {
         int k = des;          //利用前驱寻找路径
         while(k!=src)
         {
              int last = pre[k];
              capacity[last][k] -= increasement; //改变正向边的容量
              capacity[k][last] += increasement; //改变反向边的容量
              k = last;
         }
         sumflow += increasement;
    }
    return sumflow;
}
int main()
{
    int i,j;
    int start,end,ci;
    while(cin>>n>>m)
    {
        memset(capacity,0,sizeof(capacity));
        memset(flow,0,sizeof(flow));
        for(i=0;i>start>>end>>ci;
            if(start == end)               //考虑起点终点相同的情况
               continue;
            capacity[start][end] +=ci;     //此处注意可能出现多条同一起点终点的情况
        }
        cout<


Dinic算法:

最核心的内容就是多路增广。利用对整张图的分层,即源点为第一层,与源点相连的并且有容量的点为第二层,与第二层相连并且有容量的点为第三层……如果不能到达终点,说明找不到增广路径了,此时也就达到了最大流。一次BFS可以增广好几次。效率比起EK算法大大提高。 


Dinic算法最核心的内容就是多路增广。利用对整张图的分层,一次BFS可以增广好几次。效率比起EK算法大大提高。 
*/ 
int tab[250][250];//邻接矩阵 
int dis[250];//距源点距离,分层图 
int q[2000],h,r;//BFS队列 ,首,尾 
int N,M,ANS;//N:点数;M,边数 
int BFS()
{
     int i,cur;
     memset(dis,-1,sizeof(dis));//以-1填充 
     dis[1]=0;
     head=0;tail=1;
     que[0]=1;//源点入队 
     while (head0)
               {
                  dis[i]=dis[cur]+1; 
                  que[tail++]=i;
               }
     }
     if (dis[N]>0)
        return 1;
     else
        return 0;//汇点的DIS小于零,表明BFS不到汇点 
}
//Find代表一次增广,函数返回本次增广的流量,返回0表示无法增广 
int find(int x,int low)//Low是源点到现在最窄的(剩余流量最小)的边的剩余流量
{
    int i,a=0;
    if (x==N)return low;//是汇点 
    for (i=1;i<=N;i++)
    if (tab[x][i] >0 //联通 
     && dis[i]==dis[x]+1 //是分层图的下一层 
     &&(a=find(i,min(low,tab[x][i]))))//能到汇点(a <> 0) 
    {
       tab[x][i]-=a;
       tab[i][x]+=a;
       return a;
    }
    return 0;
    
}
int main()
{
    //freopen("ditch.in" ,"r",stdin );
    //freopen("ditch.out","w",stdout);
    int i,j,f,t,flow,tans;
    while (scanf("%d%d",&M,&N)!=EOF){
    memset(tab,0,sizeof(tab));
    for (i=1;i<=M;i++)
    {
        scanf("%d%d%d",&f,&t,&flow);
        tab[f][t]+=flow;
    }
    //
    ANS=0;
    while (BFS())//要不停地建立分层图,如果BFS不到汇点才结束 
    {
          while(tans=find(1,0x7fffffff))ANS+=tans;//一次BFS要不停地找增广路,直到找不到为止 
    }
    printf("%d\n",ANS);
    }
    system("pause");
}


最小费用最大流: 
在最大流有多组解时,给每条边在附上一个单位费用的量,问在满足最大流时的最小费用是多少?

最小费用最大流只是在残留网络的基础上多了个费用网络。在最大流的基础上,将费用看成路径长度,求最短路即可。 注意一开始反向边的费用为正向边的负数。 

例如POJ2516这道题,有N个供给商,M个雇主,K种物品。每个供给商对每种物品的的供给量已知,每个雇主对每种物品的需求量的已知,从不同的供给商输送不同的货物到不同的雇主手上需要不同的花费,又已知从供给商Mj送第kind种货物的单位数量到雇主Ni手上所需的单位花费。问:供给是否满足需求?若是满足,最小运费是多少?
代码很好理解,可当做模板。

int map[nMax][nMax];//map[i][j]表示对于每种k物品从i运输到j所花费的钱
int vis[nMax];//表示i是否用过
int cap[nMax][nMax];//表示i到j的最大通货量
int dis[nMax];//到i的距离
int que[nMax];//队列
int pre[nMax];//保存每一条最短增流路
int num,ans;//num最后的汇点,ans最终的答案
int spfa()//spfa求最短路径,dijstra不允许有负权,所以这里使用spfa
{
    int i,k;
    int head,tail;
    memset(vis, 0, sizeof(vis));
    for (i = 0; i <= num; ++ i)
    {
    dis[i] = inf;
    }
    dis[0] = 0;
    vis[0] = 1;
    head = tail = 0;
    que[tail++] = 0;
    while (head < tail)
    {
	    k = que[head];
	    vis[k] = 0;
	    for (i = 0; i <= num; ++ i)
	    {
	    	if (cap[k][i] && dis[i] > dis[k] + map[k][i])//如果k到i还有量,表明还可以增流,那么就求最短路
	    	{
			    dis[i] = dis[k] + map[k][i];
			    pre[i] = k;
			    if (!vis[i])
			    {
				    vis[i] = 1;
				    que[tail ++] = i;
			    }
	   		}
	    }
	    head ++;
    }
    if (dis[num] havek[i])//如果有物品供给不足,那么肯定不能完成运送
    {
    flag = 0;
    break;
    }
    }
    ans = 0;
    num = N + M + 1;
    for (k = 1; k <= K; ++ k)//计算每种货物的最小花费,然后求和
    {
	    memset(cap, 0, sizeof(cap));
	    memset(map, 0, sizeof(map));
	    for (i = 1; i <= N; ++ i)
	    {
	    for (j = 1; j <= M; ++ j)
	    {
		    scanf("%d", &map[j][M + i]);//将N个人映射到M+1-M+N区间上,这样方便建图,map j到M+i就是从地方j运送到人i的花费
		    map[M + i][j] = -map[j][M + i];
		    cap[j][M + i] = have[j][k];//j到i的量是第k种货物的在place j的最大的量
		    cap[M + i][j] = 0;
	    }
	    }
	    if (!flag)
	    {
	   		continue;
	    }
	    for (i = 1; i <= M; ++ i)
	    {
		    cap[0][i] = have[i][k];//源点到place i其实也设为第k中货物在place i的量
		    map[0][i] = map[i][0] = 0;//从原点到i花费为0
	    }
	    for (i = 1; i <= N; ++ i)
	    {
		    cap[M + i][num] = need[i][k];//从人i到汇点的量设为第i个人对第k种货物的需求量。
		    map[M + i][num] = map[num][M + i] = 0;
	    }
	    while (spfa())//求第k种货物的最小花费
	    {
	    	end();
	    }
    }
    if (flag)
    {
    printf("%d\n", ans);
    }
    else
    printf("-1\n");
    }
    return 0;
}




你可能感兴趣的:(算法)