1. 程式人生 > >網路流(最大流,最小割)基礎入門詳解

網路流(最大流,最小割)基礎入門詳解

網路流基本定義:

源點:有n個點,有m條有向邊,有一個點很特殊,只出不進,叫做源點。

匯點:另一個點也很特殊,只進不出,叫做匯點。

容量和流量:每條有向邊上有兩個量,容量和流量,從i到j的容量通常用c(u,v)表示,流量則通常是f(u,v).

殘餘網路:r(u,v) = c(u,v) – f(u,v),其中c(u,v) 表示容量,f(u,v)表示流量,r(u,v)表示殘量網路

通常可以把這些邊想象成道路,流量就是這條道路的車流量,容量就是道路可承受的最大的車流量。很顯然的,流量<=容量。而對於每個不是源點和匯點的點來說,可以類比的想象成沒有儲存功能的貨物的中轉站,所有“進入”他們的流量和等於所有從他本身“出去”的流量。

網路流的3個性質:

1、容量限制: f[u,v]<=c[u,v]

2、反對稱性:f[u,v] = - f[v,u]

3、流量平衡: 對於不是源點也不是匯點的任意結點,流入該結點的流量和等於流出該結點的流量和。

只要滿足這三個性質,就是一個合法的網路流.

最大流問題:

最大流問題,就是求在滿足網路流性質的情況下,源點 s 到匯點 t 的最大流量。
問題:給出n個河流,m個點,以及每個河流的容量,求從1到m點的最大流量。

求解思路:

首先,假如所有邊上的流量都沒有超過容量(不大於容量),那麼就把這一組流量,或者說,這個流,稱為一個可行流
對於這種沒有給出流量f的問題,稱為零流問題,即所有的流量都是0的流。
(1).我們就從這個零流開始考慮,假如有這麼一條路,這條路從源點開始一直一段一段的連到了匯點,並且,這條路上的每一段都滿足流量<=容量。
(2).那麼,我們一定能找到這條路上的每一段的流量的值當中的最小值flow。我們把這條路上每一段的流量都減去這個flow,一定可以保證這個流依然是可行流,這是顯然的,然後再將這條路上的每一段的反向都加上這個flow


(3).然後將每次的流量都加上flow,這樣我們就得到了一個更大的流,他的流量是之前的流量+flow,而這條路就叫做增廣路。我們不斷地從起點開始尋找增廣路,每次都對其進行增廣,直到源點和匯點不連通,也就是找不到增廣路為止。
(4).當找不到增廣路的時候,當前的流量就是最大流,這個結論非常重要。

注意事項:

1.反向邊系列:
反向邊求法:u->v的反向邊f(v,u)=c(v,u)-f(v,u)=c(v,u)+f(u,v);
為什麼要求反向邊:在做增廣路時可能會阻塞後面的增廣路,或者說,做增廣路本來是有個順序才能找完最大流的。但我們是任意找的,為了修正,就每次將流量加在了反向弧上,讓後面的流能夠進行自我調整。
例如:下邊這個例子1位源點,4為匯點
這裡寫圖片描述


如果我們第一次找的是1->2->3->4這條增廣路徑可以得到一個可行流為1,執行操作(2)後圖形變成了如下:
這裡寫圖片描述
這時我們再找增廣路徑,你會發現已經無法從1到達4了,所以這個時候的最大流就是1了,但顯然這是錯誤的,如果我們分別走1->2->3和1->3->4這條路就可以得到一個為2的可行流。
為什麼會出現這樣的錯誤呢?
那是因為我們找從1到4的路徑是隨機的沒有任何技巧可言,並不能保證從1到4的某條路就是最大流,但可以確定的是這條路徑很可能會破壞其他路徑,而破壞的路徑可能是構成最大流的其中一條路徑。所以這個時候我們要給程式一個後悔的機會,即新增一條反向路徑。具體看下邊這個過程:
我們在找到1->2->3->4這條增廣路徑後需要修改原圖如下:
這裡寫圖片描述
這樣修改圖後,可以發現,這個時候還存在增廣路徑1->3->2->4,然後修改圖如下:
這裡寫圖片描述
加上之前的1->2->3->4,此時的最大流為2。
那麼,這麼做為什麼會是對的呢?
事實上,當我們第二次的增廣路走3-2這條反向邊的時候,就相當於把2-3這條正向邊已經是用了的流量給“退”了回去,不走2-3這條路,而改走從2點出發的其他的路也就是2-4。
如果這裡沒有2-4怎麼辦?
這時假如沒有2-4這條路的話,最終這條增廣路也不會存在,因為他根本不能走到匯點
同時本來在3-4上的流量由1-3-4這條路來“接管”。而最終2-3這條路正向流量1,反向流量1,等於沒有流。
CODE:

#include <bits/stdc++.h>
#define INF 0x3f3f3f3f

using namespace std;

int Map[210][210];
int pre[210];
int flow[210];
int n,m;
int bfs(int s,int t)
{
    queue<int> Q;
    memset(pre,-1,sizeof(pre));
    pre[s] = 0;
    flow[s] = INF;
    Q.push(s);
    while(!Q.empty()){
        int index = Q.front();
        Q.pop();
        if(index == t)
            break;
        for(int i=1;i<=m;i++){
            if(i!=s && Map[index][i]>0&&pre[i]==-1){
                pre[i] = index;
                flow[i] = min(flow[index],Map[index][i]);
                Q.push(i);
            }
        }
    }
    if(pre[t] == -1)///到不了匯點
        return -1;
    else
        return flow[t];
}
int Max_flow(int s,int t)
{
    int inc=0,sumflow=0;
    while(bfs(s,t)!=-1){
        inc = bfs(s,t);
        int k = t;
        while(k!=s){
            int last = pre[k];
            Map[last][k] -= inc;
            Map[k][last] += inc;
            k = last;
        }
        cout<<"曾廣:"<<inc<<endl;
        sumflow += inc;
    }
    return sumflow;
}
int main()
{
    int u,v,c;
    while(~scanf("%d %d",&n,&m)){
        memset(Map,0,sizeof(Map));
        memset(flow,0,sizeof(flow));
        for(int i=0;i<n;i++){
            scanf("%d %d %d",&u,&v,&c);
            if(u == v) continue;
            Map[u][v] += c;
        }
        cout<<Max_flow(1,m)<<endl;
    }
    return 0;
}

最小割

割的定義:設Ci為網路G中一些弧的集合,若從G中刪去Ci中的所有弧能使得從源點Vs到匯點Vt的路集為空集時,稱Ci為Vs和Vt間的一個割。(注意,必須是刪除Ci中的所有邊)
最小割:圖中所有的割中,邊權值和最小的割為最小割。

最小割與最大流的關係

我以下邊這個例子來說明最大流和最小割之間的關係:
這裡寫圖片描述
從1到4,中間經過2,3兩節點,問此時的最大流是多少?
首先找一條從1到4的路徑[1,2,4],該路徑的最大流量是min(2,3)=2,因為[1,2]上面的容量已經被用了,所以路徑[1,2,3,4]就行不通了,割去[1,2]後圖變成了以下形式:
這裡位 述
此時再找從1到4的路徑[1,3,4],路徑的最大流量是min(3,6)=3.割去[b,t]後,圖如下:
這裡寫圖片描述
此時就不存在從S到t的可行路徑了,則結束最大流的查詢。此時的最大流是2+3=5,被割的邊容量和是2+3=5,即最大流=最小割。從這兩個例子我們已經能理解最大流和最小割大體的含義了,也發現最大流的確和最小割是相等的從數值上來看。但只從這兩個小例子就證明最大流和最小割相等是絕對不嚴格的,嚴格的數學證明可自行百度相關資料。