網絡流最大流EK和Dinic入門算法

網絡流基礎入門,這裏不說那些證明過程了,直接個人見解。

首先:最大流,顧名思義,是從源點出發,經過若干條路徑,最後到達匯點的所有流的和。而這裏最大流的確定條件是,當前的網絡中不存在增廣路了。那什麼是增廣路呢?就是當進行若干次操作後,當前網絡還存在使最大流更大的路徑,那麼就是增廣路了。如果當前網絡不存在增廣路的話,那麼我們就無法再增加最大流了,那也就是最大流已經求解出來了。在求最大流的過程中,我們還要構建殘餘網絡,還有一個反向流,據說是可以對之前的某些操作進行“反悔”,從而獲得更大的流。

好了,下邊是EK算法,書上也說是最短增廣路算法,SAP算法。因爲這裏首先用了BFS算法。這個算法的作用是啥呢?就是從源點出發,運用隊列,通過層次遍歷,一直遍歷到匯點。由於這個操作是基於BFS實現,那麼求出的就是到匯點的最短路徑。我們再來分析一遍。在BFS的過程中,我們還要更新每個點到下一個點可允許通過的最小流。因爲一條增廣路的最小流量肯定是這條路徑上的最小容量,如果假設當前結點的流是F[CUR],那麼下一個點CUR+1可通過的流就是min(F[CUR],Edge[CUR][CUR+1]),同時還要記住pre[CUR+1]=CUR,因爲當我們找完所有路徑到匯點的流之後,還要反過來更新每條邊的容量,那麼這裏pre的作用就記錄前驅了。當我們通過BFS找完當前網絡所有到匯點的路徑之後,假設匯點T的F[T]!=0,也就是還有到達匯點的流,並且這個到匯點的F[T]流肯定是某條增廣路上的能夠增加的流,然後我們就可以由匯點反推回來更新每條邊的容量,噹噹前的可更新的流更新完之後,就接着就行BFS,繼續找增廣路,知道F[T]=0,此時就沒有到達匯點的流,那麼當前網絡也就不存在增廣路了。現在來分析一下EK的時間複雜度,在BFS的過程中,我們是從每個頂點開始BFS,那麼當n個頂點,m條邊的時候,此時的時間複雜度是O(n*m),然後在更新流的過程中,我們也是通過路徑即邊來更新,所以EK算法時間複雜度爲O(n*m*m);  注意這裏的n是頂點數,m是邊數(下邊的代碼剛好反過來)

下邊貼上EK的算法模板,個人習慣寫的,可以參考:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<iostream>
#include<queue>
using namespace std;
int pre[250];
int n,m;
int edge[250][205];
int BFS(){
    int value[205];
    for(int i=1;i<=m;i++){
        value[i]=0;
        pre[i]=-1;
    }
    value[1]=0x3f3f3f3f;
    pre[1]=-1;
    queue<int> q;
    q.push(1);
    while(q.empty()==0){
        int u=q.front();
        q.pop();
        for(int i=1;i<=m;i++){
            if(edge[u][i]>0&&value[i]==0){
                value[i]=min(edge[u][i],value[u]);
                pre[i]=u;
                q.push(i);
            }
        }
    }
    return value[m];
}
int EK(){
    int ans=0;
    int add;
    while(add=BFS()){
        ans+=add;
        int u=m;
        while(pre[u]!=-1){
            int v=pre[u];
            edge[u][v]+=add;
            edge[v][u]-=add;
            u=v;
        }
    }
    return ans;
}
int main(){
    //freopen("test.txt","r",stdin);
    while(~scanf("%d%d",&n,&m)){
        memset(edge,0,sizeof(edge));
        for(int i=1;i<=n;i++){
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            edge[u][v]+=w;

        }
        printf("%d\n",EK());
    }
    return 0;
}

OK,大概講完EK,現在來討論Dinic。Dinic又稱連續最短增廣路算法,這個算法是由BFS+DFS寫成的。其實BFS的過程跟EK的過程差不多,只是這裏不在BFS的過程中尋找流,而是做層次標記,從源點開始,不斷做標記,這裏也保持最短路的特性,如果當前點可以做標記的話,說明上一個點是通過某個大小的流到達當前點的,那麼如果匯點可以做標記的話,說明是存在從源點到匯點的流,至於這個流的大小並不在BFS中求出。當做完BFS後,如果匯點可以被標記,那麼我們就要從源點開始,做DFS操作,這個操作當讓是基於最短路也是層次上實現的,下面來看看DFS的過程

int DFS(int s,int Max){
    int ans=0;
    if(s==m){
        return Max;
    }
    for(int i=1;i<=m;i++){
        if(edge[s][i]>0&&level[s]+1==level[i]){
            int Min=min(Max-ans,edge[s][i]);
            int f=DFS(i,Min);
            edge[s][i]-=f;
            edge[i][s]+=f;
            ans+=f;
            if(ans==Max){
                return ans;
            }
        }
    }
    return ans;
}
s是當前點,Max是上一個點傳到當前點的流,m是匯點。我們從for循環分析起。如果當前點可以到i點存在流,並且i點是s點的下一個層次的點(保持最短路的特性),那麼我們就可以從i點繼續DFS,好了,問題來了。這個   int Min=min(Max-ans,edge[s][i]);   有什麼用呢?我們來分析下,Max是上一個點傳下來的流量,ans是s這個層次除開i點外其他點使用完的流量,那麼Max-ans當然就是可以供i點使用的流量了,,edge[s][i]當然就是流過i點的流量了,然後我們要取其中的最小值,繼續傳給下一個點,也就是繼續DFS。那麼DFS結束的條件當然就是遇到匯點了。如果s是匯點,Max是從上一個點到s點的流量,也就是最終流入匯點的流量了,那麼本次DFS的過程也就結束了。然後就遞歸回去更新流,然後再DFS,知道該層次網絡中沒有增廣路,那麼就重新BFS規劃層次網絡,然後判斷DFS。就沒了。大家有沒有發現Dinic和EK的一個區別,EK進行BFS,只能找一條增廣路,Dinic進行一次BFS,存在找到多條增廣路的可能性,這也就是說可以對該網絡進行連續增廣。那分析一下Dinic的時間複雜度,BFS花費的時間依舊是O(n*m),在DFS的過程中,我們要進行層次遍歷,那肯定是對於頂點進行層次標記,所以DFS的過程我們是對於頂點的遍歷,那麼DFS的時間是O(n),所以整個Dinic的時間複雜度是O(n*n*m)

注意這裏的n是頂點數,m是邊數(下邊的代碼剛好反過來)

下邊貼上EK的算法模板,個人習慣寫的,可以參考:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<iostream>
#include<queue>
using namespace std;
int pre[250];
int n,m;
int edge[250][205];
int level[250];
int BFS(){
    memset(level,0,sizeof(level));
    memset(pre,0,sizeof(pre));
    queue<int> q;
    q.push(1);
    level[1]=1;
    while(q.empty()==0){
        int u=q.front();
        q.pop();
        for(int i=1;i<=m;i++){
            if(edge[u][i]>0&&level[i]==0){
                level[i]=level[u]+1;
                q.push(i);
            }
        }
    }
    return level[m];
}
int DFS(int s,int Max){
    int ans=0;
    if(s==m){
        return Max;
    }
    for(int i=1;i<=m;i++){
        if(edge[s][i]>0&&level[s]+1==level[i]){
            int Min=min(Max-ans,edge[s][i]);
            int f=DFS(i,Min);
            edge[s][i]-=f;
            edge[i][s]+=f;
            ans+=f;
            if(ans==Max){
                return ans;
            }
        }
    }
    return ans;
}
int Dinic(){
    int ans=0;
    while(BFS()){
        ans+=DFS(1,0x3f3f3f3f);
    }
    return ans;
}
int main(){
    //freopen("test.txt","r",stdin);
    while(~scanf("%d%d",&n,&m)){
        memset(edge,0,sizeof(edge));
        for(int i=1;i<=n;i++){
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            edge[u][v]+=w;
        }
        printf("%d\n",Dinic());
    }
    return 0;
}

經典最大流模板題:POJ - 1273 http://poj.org/problem?id=1273
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章