最大流五大算法簡述


網絡最大流各算法總結

網絡最大流的算法共有兩大類5種算法 總體如下表:n為頂點數,m為弧的數目,U代表各條弧的最大容量

算法名稱 復雜度 算法概要
一般增廣路算法 \(O(nmU)\) 采取標號法每次在容量網絡中尋找一條增廣路進行增廣(或在殘留網絡中每次任意尋找一條增廣路進行增廣),直至不存在增廣路為止。
最短增廣路算法 \(O(nm^2)\) 每個階段:在層次網絡中,不斷用BFS算法進行增廣直至不存在增廣路為止。如果匯點不在層次網絡中,算法結束。
連續最短增廣路算法(Dinic) \(O(n^2m)\) 在最短增廣路算法的基礎上改造:在每個階段,用一個dfs過程實現多次增廣。如果匯點不在層次網絡中,則算法結束。
一般預流推進算法 \(O (n^2m)\) 維護一個預流,不斷地對活躍頂點執行推進(Push)操作或重標號(Relabel)操作來調整這個預流,直到不能操作。
最高標號預流推進算法 \(O( n^2\sqrt m)\) 每次檢查具有最高標號的活躍結點
  • 一般增廣路算法(Ford-Fulkerson):

    1) 標號過程 每個頂點的標號有兩個量:流入該頂點的流量alpha[u] 指明標號從哪個頂點得到pre[u]

    2)調整過程 在每次得到最后V的標號后 根據 a = alpha[n-1] 來對這條增廣路進行優化

\(f(u,v) = f(u,v) + a\ when<u,v>\in P+\)

\(f(u,v) = f(u,v)+a\ when <u,v>\in P-\)

$ f(u,v) = f(u,v) \ when <u,v>\notin P$

通過bfs來進行多次增廣路的優化 代碼如下:

#include<bits/stdc++.h>
using namespace std;
#define MAXN 1000
#define INF 1000000//根據題目條件改變
struct ArcType {
    int c,f;//容量,流量
};
ArcType Edge[MAXN][MAXN];
int n,m;
int flag[MAXN];//頂點狀態:-1未標號 0已標號未檢查 1已標號以檢查
int pre[MAXN];//標號的第一個分量:指明從哪個點得到,以便找出可改進量和增廣路徑
int alpha[MAXN];//標號的第二個分量:可改進量a
queue<int > q;
int v;
void ford() {
    for(;;) {//標號直至不存在可改進路
        memset(flag,0xff,sizeof(flag));//均初始化為-1
        memset(pre,0xff,sizeof(pre));
        memset(alpha,0xff,sizeof(alpha));
        flag[0] = pre[0] = 0; alpha[0] = INF;//對源點標號
        while(!q.empty()) q.pop();//清空隊列
        q.push(0);
        while(!q.empty() && flag[n-1] == -1) {
            v = q.front();q.pop();
            for(int i = 0;i < n;i++) {
                if(flag[i] == -1) {//正向且未飽和
                    if(Edge[v][i].c < INF && Edge[v][i].f < Edge[v][i].c) {
                        flag[i] = 0;pre[i] = v;
                        alpha[i] = min(alpha[v],Edge[v][i].c - Edge[v][i].f);
                        q.push(i);
                    }
                    else if(Edge[i][v].c < INF && Edge[i][v].f > 0) {//反向且非0
                        flag[i] = 0;pre[i] = -v;
                        alpha[i] = min(Edge[i][v].f,alpha[v]);
                        q.push(i);
                    }
                }
            }
            flag[v] = 1;
        }
        if(flag[n-1] == -1 || alpha[n-1] == 0) break;//當匯點無需調整,退出循環
        int k1 = n-1,k2 = abs(pre[k1]);
        int a = alpha[n-1];    
        while(1) {//沿着路徑改進
            if(Edge[k2][k1].f < INF) Edge[k2][k1].f += a;
            else Edge[k1][k2].f -= a;
            if(k2 == 0) break;
            k1 = k2; k2 = abs(pre[k2]);
        }
    }
    int maxFlow = 0;
    for(int i = 0;i < n;i++) {
        for(int j = 0;j < n;j++) {
            if(i == 0 && Edge[i][j].f < INF) maxFlow+=Edge[i][j].f;//求出源點的流出量
            if(Edge[i][j].f < INF) printf("%d->%d:%d\n",i,j,Edge[i][j].f);
        }
    }
    printf("maxFlow:%d\n",maxFlow);
} 
int main() {
    int u,v,c,f;
    scanf("%d%d",&n,&m);
    for(int i = 0;i < n;i++) {
        for(int j = 0;j < n;j++) Edge[i][j].c = Edge[i][j].f = INF;
    }
    for(int i = 0;i < m;i++) {
        scanf("%d%d%d%d",&u,&v,&c,&f);
        Edge[u][v].c = c;Edge[u][v].f = f;
    }
    ford();
    return 0;
}

復雜度簡要分析:

​ 很明顯,如果容量網絡中各弧的容量和初始流量均為正整數,則Ford-Fulkerson算法每增廣一次,流量至少會增加一個單位,因此Ford-Fulkerson算法肯定能在有限的步驟內使得網絡流達到最大。類似的理由可以說明如果弧上的容量為有理數時,也可在有限的步驟內使得網絡流達到最大。但是如果弧上的容量可以是無理數,則該算法不一定在有限步內終止。

​ 由於割$ { Vs} , V-{ Vs}$ 中前向弧的條數最多為n條,因此最大流流量|F|的上界為nU(U表示網絡中各個弧的最大容量)。此外,由於每次增廣最多需要對所有弧檢查一遍,所以Ford-Fulkerson算法的時間復雜度為$ O(mnU)$ 。

例題:Poj1149

  • 最短增廣路算法:

    最短增廣路的思想:

    ​ (1)初始化容量網絡和網絡流。

    ​ (2)構造殘留網絡和層次網絡,若匯點不在層次網絡中,則算法結束。

    ​ (3)在層次網絡中不斷用BFS增廣,直到層次網絡中沒有增廣路為止;每次增廣完畢,在層次網絡中要去掉因改進流量而導致飽和的弧。

    ​ (4)轉步驟(2)

  • 連續最短增廣路算法(Dinic算法):

    Dinic算法思路:

    ​ Dinic算法的思想也是分階段地在層次網絡中增廣。它與最短路算法不同之處是:最短路增廣每個階段執行完一次BFS增廣后,要重新啟動BFS從源點Vs開始尋找另一條增廣路;而在Dinic算法中,只需一次dfs過程就可以實現多次增廣,這是Dinic算法的巧妙之處。Dinic算法的具體步驟如下:

    ​ (1)初始化容量網絡和網絡流

    ​ (2)構造殘留網絡和層次網絡,若匯點不在層次網絡中,則算法結束。

    ​ (3)在層次網絡中用一次dfs過程進行增廣,dfs過程執行完畢,則該階段的增廣也執行完畢。

    ​ (4)轉步驟(2)

    在Dinic算法中,只有第(3)步和最短增廣路算法不同。效率得到非常大的提高。

    模板如下:

    const int maxn=500+10;
    struct Edge {
        int from,to,cap,flow;
        Edge(){}
        Edge(int f,int t,int c,int fl):from(f),to(t),cap(c),flow(fl){}
    };
    struct Dinic {
        int n,m,s,t;
        vector<Edge> edges;
        vector<int> G[maxn];
        int d[maxn];
        int cur[maxn];
        bool vis[maxn];
     
        void init(int n,int s,int t) {
            this->n=n, this->s=s, this->t=t;
            edges.clear();
            for(int i=0;i<n;i++) G[i].clear();
        }
        void AddEdge(int from,int to,int cap) {
            edges.push_back( Edge(from,to,cap,0) );
            edges.push_back( Edge(to,from,0,0) );
            m=edges.size();
            G[from].push_back(m-2);
            G[to].push_back(m-1);
        }
        bool BFS() {
            queue<int> Q;
            memset(vis,0,sizeof(vis));
            vis[s]=true;
            d[s]=0;
            Q.push(s);
            while(!Q.empty()) {
                int x=Q.front(); Q.pop();
                for(int i=0;i<G[x].size();i++) {
                    Edge e=edges[G[x][i]];
                    if(!vis[e.to] && e.cap>e.flow) {
                        vis[e.to]=true;
                        d[e.to] = d[x]+1;
                        Q.push(e.to);
                    }
                }
            }
            return vis[t];
        }
     
        int DFS(int x,int a) {
            if(x==t || a==0) return a;
            int flow=0,f;
            for(int& i=cur[x];i<G[x].size();++i) {
                Edge& e=edges[G[x][i]];
                if(d[e.to]==d[x]+1 && (f=DFS(e.to, min(a,e.cap-e.flow) ) )>0 ) {
                    e.flow+=f;
                    edges[G[x][i]^1].flow-=f;
                    flow+=f;
                    a-=f;
                    if(a==0) break;
                }
            }
            return flow;
        }
     
        int Max_Flow() {
            int flow=0;
            while(BFS()) {
                memset(cur,0,sizeof(cur));
                flow += DFS(s,INF);
            }
            return flow;
        }
    }DC;
     
    
  • 一般預流推進算法

    1.增廣路算法的缺點:

    ​ 增廣路算法是找到增廣路后,立即沿增廣路對網絡流進行增廣。每一次增廣可能需要對最多n-1條弧進行操作,因此,每次增廣的復雜度為\(O(n)\) ,在有些情況下,這個操作的代價是很高的。

    2.距離標號

    ​ 對於一個殘留網絡G',如何確定其精確的距離標號呢?可以從匯點Vt開始,對弧進行廣度優先搜索,這一過程的復雜度為\(O(m)\)

預流:設\(f = \{ f(u,v)\}\) 是容量網絡的一個網絡流,如果G的每一條邊弧都滿足: \(0\leq f(u,v) \leq c(u,v)\)

另外,除源點匯點以外每個頂點u的盈余e(u)都滿足:\(e(u) \geq 0\) 則稱該網絡流是G的預流。

​ 預流推進通過不斷的對活躍節點的改進來達到最大流

模板如下:

using namespace std;
const int maxn = 110;
const int maxf = 0x7fffffff;
int n,np,nc,m;
int resi[maxn][maxn];
deque<int> act;
int h[maxn];
int ef[maxn];
int s,t,V;
void push_relabel() {
    int sum = 0;
    int u,v,p;
    for(int i = 1;i <= V;i++) h[i] = 0;
    h[s] = V;
    memset(ef,0,sizeof(ef));
    ef[s] = maxf;ef[t] = -maxf;
    act.push_front(s);
    while(!act.empty()) {
        u = act.back();
        act.pop_back();
        for(int i = 1;i <= V;i++) {
            v = i;
            if(resi[u][v] < ef[u]) p = resi[u][v];
            else p = ef[u];
            if(p > 0 && (u == s || h[u] == h[v] + 1)) {
                resi[u][v]-=p;resi[v][u]+=p;
                if(v == t) sum+=p;
                ef[u]-=p;ef[v]+=p;
                if(v != s && v != t) act.push_front(v);
            }
        }
        if(u != s && u != t && ef[u] > 0) {
            h[u]++;
            act.push_front(u);
        }
    } 
    printf("%d\n",sum);
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM