1. 程式人生 > >高階篇——Dinic最大流演算法

高階篇——Dinic最大流演算法

起於源點s,止於匯點t,解決最大流問題的過程就是不斷尋找增廣路徑的過程。最大流問題專用術語先一一解釋。

1.增廣路徑:從源點到匯點不一定只有一條路,要想做到流到匯點的流量最大,必須使得每一條能到匯點的路徑都能被流經。每一條從源點到匯點的路徑便是一條增廣路徑。

2.反向弧:每從一點到達另一點,都需要在相反的方向上設定一條反向邊,每條反向邊的作用可以理解為給演算法一個可以反悔的機會。

3.殘餘網路:當每從一點到達其下一個點時,這個點可以經過的流量需要減去實際經過的流量,以便往後尋找增廣路徑時不會出現流量大於可行流量的情況。這樣處理過的流量網路便是殘餘網路。

最大流問題較為高效的方法是Dinic和SAP,Dinic方法結合了中級篇講的BFS和DFS演算法的優勢,通過BFS演算法尋找增廣路徑,DFS演算法求得每條可行流的最大值。

先來講講如何尋找增廣路徑。

增廣路徑用BFS演算法。設定陣列dis[ ],表示每個點距離源點的距離。初始化為-1,源點dis[ 1 ] =1,之後開始遍歷,一要是能到達且該點對應dis值為-1,便將該點dis值設為前點dis值加1。這個有什麼用?等到了DFS的時候就知道了 。如果最終匯點的dis值不為-1,及說明會點可以到達,增廣路徑存在,返回true,否則返回false。

int BFS()
{
    queue<int>que;
    memset(dis,-1,sizeof(dis));//每次尋找增廣路徑都需要將dis陣列重置
    dis[1]=0;   //起始點為0
    que.push(1);
    while(!que.empty())
    {
        int cur=que.front();
        que.pop();
        for(int i=1;i<=n;i++)
        {
            if(c[cur][i]&&dis[i]<0) //如果可行且該點尚未被標註
            {
                dis[i]=dis[cur]+1;  //改變dis值
                que.push(i);        //入隊繼續遍歷
            }
        }
    }
    if(dis[n]>0) return true;  //匯點值不為-1,說明增廣路徑存在
    return false;              //反之不存在
}

BFS負責尋找增廣路徑,DFS計算可行的最大流,此時dis[ ]就派上用場了。一旦經過BFS發現存在可行流,現在要做的便是從源點出發,只要遇到可以到達且dis[ 終點 ]=dis[ 起點 ]+1便繼續對該終點進行DFS搜尋。也就是說DFS是一個不斷遞迴的過程。當終點為匯點時返回對應的流量值,並構建殘餘網路和反向弧。
int DFS(int point,int MAX) //point為當前遍歷的節點,MAX為最大可行流量,MAX初始為INF
{
    int a;
    if(point==n) return MAX; //當前點為匯點時返回即可
    for(int i=1;i<=n;i++)
    {
        //當路徑可行,對終點進行DFS
        if(c[point][i]&&dis[i]==dis[point]+1&&(a=DFS(i,min(MAX,c[point][i]))))
        {
            c[point][i]-=a; //構建殘餘網路
            c[i][point]+=a; //建立反向弧
            return a;
        }
    }
    return 0;
}

以POJ 1273為例:http://poj.org/problem?id=1273

題意:m條路徑,n個點,每條路徑輸入起點,終點和流量,求源點到匯點的最大流量

分析:這題直接套模板就行

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <queue>
#define INF 9999999
using namespace std;
int n,m;
int c[205][205],dis[205]; //c陣列為原始網路

int BFS()
{
    queue<int>que;
    memset(dis,-1,sizeof(dis));//每次尋找增廣路徑都需要將dis陣列重置
    dis[1]=0;   //起始點為0
    que.push(1);
    while(!que.empty())
    {
        int cur=que.front();
        que.pop();
        for(int i=1;i<=n;i++)
        {
            if(c[cur][i]&&dis[i]<0) //如果可行且該點尚未被標註
            {
                dis[i]=dis[cur]+1;  //改變dis值
                que.push(i);        //入隊繼續遍歷
            }
        }
    }
    if(dis[n]>0) return true;  //匯點值不為-1,說明增廣路徑存在
    return false;              //反之不存在
}
int DFS(int point,int MAX) //point為當前遍歷的節點,MAX為最大可行流量,MAX初始為INF
{
    int a;
    if(point==n) return MAX; //當前點為匯點時返回即可
    for(int i=1;i<=n;i++)
    {
        //當路徑可行,對終點進行DFS
        if(c[point][i]&&dis[i]==dis[point]+1&&(a=DFS(i,min(MAX,c[point][i]))))
        {
            c[point][i]-=a; //構建殘餘網路
            c[i][point]+=a; //建立反向弧
            return a;
        }
    }
    return 0;
}

int main()
{
    while(cin>>m>>n)
    {
        memset(c,0,sizeof(c));
        while(m--) //構建原始網路
        {
            int u,v,w;
            cin>>u>>v>>w;
            c[u][v]+=w;
        }
        int sum=0;
        while(BFS()) //當增廣路徑存在進行DFS
        {
            int s=DFS(1,INF); //從源點出發,初始最大可行流設為無窮大
            sum+=s;  //結果加上得到的最大可行流
        }
        cout<<sum<<endl;
    }
    return 0;
}