日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

网络流(最大流)基础入门

發(fā)布時(shí)間:2023/12/20 编程问答 19 豆豆
生活随笔 收集整理的這篇文章主要介紹了 网络流(最大流)基础入门 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

好不容易大概搞懂了網(wǎng)絡(luò)流,寫個(gè)博客鞏固一下(盜了點(diǎn)圖,請圖主原諒)
**

定義 網(wǎng)絡(luò)流與最大流

**

網(wǎng)絡(luò)流是指給定一個(gè)有向圖,和兩個(gè)點(diǎn)–源點(diǎn)S和匯點(diǎn)T,點(diǎn)之間有連邊,
每條邊有一個(gè)容量限制,可以看作水管,網(wǎng)絡(luò)流就是指由S點(diǎn)流到T點(diǎn)的一個(gè)可行流。
最大流就是指所有可行流里面最大的流。

通俗的講,就是由若干個(gè)運(yùn)貨點(diǎn),一個(gè)是起點(diǎn),一個(gè)是終點(diǎn),有一些運(yùn)貨點(diǎn)由路相連,每條路有容量限制,走過那條路時(shí)運(yùn)送的貨物不能超過其中的容量限制,求最大流就是求從起點(diǎn)運(yùn)送盡量多的貨物到終點(diǎn),到達(dá)終點(diǎn)的貨物個(gè)數(shù)。

舉個(gè)例子:

此時(shí)最大流為4。

同時(shí),網(wǎng)絡(luò)流也有三個(gè)性質(zhì):
1、容量限制: f[u,v]<=c[u,v]
2、反對稱性:f[u,v] = - f[v,u]
3、流量平衡: 對于不是源點(diǎn)也不是匯點(diǎn)的任意結(jié)點(diǎn),流入該結(jié)點(diǎn)的流量和等于流出該結(jié)點(diǎn)的流量和。
網(wǎng)絡(luò)流必須滿足這三個(gè)性質(zhì)
第一個(gè)很容易理解,就是運(yùn)送的貨物不能夠超出限制
第二個(gè)也很容易理解,v向u流入x,v減少,u增加,如果從反方向看,就意味著
u減少-x,v增加-x,也就是u向v流入-x
第三個(gè)就是除了源點(diǎn)和匯點(diǎn)的運(yùn)貨點(diǎn)不能夠囤貨,收到貨物就要發(fā)出去。

最大流算法Dinic

最大流的算法有很多,有EK,Dinic,ISAP等
現(xiàn)在就介紹一種時(shí)間效率很高的算法–Dinic,
概念

其實(shí)EK,Dinic都有一個(gè)統(tǒng)一的思路–找增廣路,
相信同學(xué)們在學(xué)二分圖的時(shí)候已經(jīng)聽過這個(gè)名詞,

增廣路的意思是在當(dāng)前網(wǎng)絡(luò)之后找到一條能夠從源點(diǎn)到匯點(diǎn)能運(yùn)更多貨物的路徑。當(dāng)一條邊被增廣之后(即它是增廣路的一部分,或者說增廣路通過這條邊),這條邊還能通過的流量,叫做剩余流量,修改之后的圖稱為殘量網(wǎng)絡(luò)。

拿剛才的作為例子:

所以這類算法的思路就是,一直找增廣路,找到?jīng)]有為止
但是如果走完增廣路,有更優(yōu)的流法怎么辦?例如說:

又圖可知,最大流為4,
但在這個(gè)圖里面,若算法選擇了紅色路徑為增廣路,

此時(shí)流為2,這樣走之后就找不到增廣路了,因此最大流為2,顯然不對。

正確路徑應(yīng)是:

所以我們不能夠單純的尋找,需要給程序提供一個(gè)反悔的機(jī)會,
在這里我們引入一個(gè)反向弧的概念,也就是在增廣的同時(shí),給增廣路上的邊的反向邊容量增加所增廣流量:

意思是,程序可以反悔,把所流的流量流回去。
按照這個(gè)步驟,繼續(xù)執(zhí)行下去,程序就會找到另一條增廣路,從而得到正確答案。

將原本紅色的增廣流量流回去到1點(diǎn),然后換一條路徑到-2-T,此時(shí)藍(lán)色就可以走4-T增廣,其實(shí)是一種“偷梁換柱”的手法。
引入反向弧后,就能解決這個(gè)問題。

算法步驟

Dinic算法是在找增廣路 前提下加了幾個(gè)優(yōu)化,

先給一個(gè)步驟,后面詳細(xì)解釋:
1.用BFS建立分層圖
2.用DFS的方法尋找一條由源點(diǎn)到匯點(diǎn)的路徑,獲得這條路徑的流量x.
根據(jù)這條路徑修改整個(gè)圖,將所經(jīng)之處正向邊流量減少x,反向邊流量增加x
重復(fù)步驟2,直到DFS找不到新的路徑時(shí),重復(fù)步驟1

1、BFS

為了避免走重復(fù)的路徑,
首先從S點(diǎn)BFS,將整個(gè)圖分成若干層,變成層次圖,S點(diǎn)為第0層,
定義dis[u]為u所在的層數(shù),也就是至源點(diǎn)的最短距離。
(tips:如果u點(diǎn)到下一個(gè)節(jié)點(diǎn)v的邊剩余容量為0,則算法忽略此邊,因?yàn)槭S嗳萘繛?已經(jīng)無法增廣)

例如說:

如果在BFS的時(shí)候沒有遍歷過T點(diǎn),則說明源點(diǎn)與匯點(diǎn)不連通,函數(shù)返回0,算法結(jié)束。
有的話,返回1,代表建立成功,那么dis數(shù)組可以為接下來的DFS尋找增廣路提供優(yōu)化。

2、DFS

在BFS之后就要從源點(diǎn)開始DFS找增廣路,
DFS(u,exp)表示經(jīng)過u點(diǎn)時(shí)當(dāng)前流量為exp。
剛開始時(shí)從源點(diǎn)DFS時(shí),exp=∞,表示從該點(diǎn)流出的流量可以為無限大。
在DFS中遍歷u所連接的點(diǎn)v,dis[v]必須等于dis[u]+1,否則會多走沒有必要的路徑。
可以利用DFS進(jìn)行多路增廣來保證時(shí)間效率,即一次增廣多條路。

因邊有流量限制,所以DFS(u,exp)

[Math Processing Error]
當(dāng)u點(diǎn)為T時(shí),就等于找到一條由S點(diǎn)到T點(diǎn)的增廣路,返回exp至上一層。
當(dāng)u點(diǎn)不為T時(shí),定義一個(gè)值flow為在u點(diǎn)所增廣的流量,初始值為0
同時(shí)要找下一個(gè)節(jié)點(diǎn)以DFS(節(jié)點(diǎn)是一條以u為起點(diǎn)的邊上的終點(diǎn)),
但需要一些限制,首先dis[v]=dis[u]+1,而且該邊的剩余容量不能為0,否則肯定不能夠增廣更多的流量。
對于每次節(jié)點(diǎn)的DFS,如果該DFS可以增廣的流量為0,說明增廣失敗,就直接跳過后面,找下一個(gè)節(jié)點(diǎn)v。
不為0的話,當(dāng)前exp就減去增廣路的流量,代表著在u點(diǎn)還可以流的流量,如果exp已經(jīng)等于0就代表著在該點(diǎn)已經(jīng)沒有更多流量可以流過去了,必須退出增廣。
flow加上該增廣路增廣流量。
同時(shí),正向邊減去流量,代表剩余流量,反向邊加上流量(反向弧)

最后,該點(diǎn)所有的節(jié)點(diǎn)遍歷完后,返回該點(diǎn)所增廣的流量flow給上一層。

整個(gè)DFS結(jié)束后,最大流累加器ans加上S點(diǎn)的增廣流量,因?yàn)橥V沽薉FS,
所以已經(jīng)沒有路可以增廣,所以重新BFS給當(dāng)前殘余網(wǎng)絡(luò)建層次圖。
如果BFS返回0,即S點(diǎn)和T點(diǎn)不聯(lián)通時(shí),直接退出,輸出當(dāng)前ans,
也就是最大流

即偽代碼:

while(BFS()):
ans += DFS(S,inf)

這樣,Dinic算法的步驟就結(jié)束了。

模擬

讓我們來模擬一下吧:
以下面這個(gè)圖作為例子:

首先用BFS分層,層次圖如下:

分層后開始由S點(diǎn)DFS,
首先DFS(S,∞):
找到下一個(gè)節(jié)點(diǎn)3,dis[3]=dis[S]+1,所以可以,
此時(shí)流進(jìn)3的最大流量只能是現(xiàn)在走過路徑所允許的容量和當(dāng)前點(diǎn)點(diǎn)流量的最小值,即min(∞,5)=5,即進(jìn)入DFS(3,5)。

DFS(3,5):
找到下一個(gè)節(jié)點(diǎn)T,此時(shí)流進(jìn)T最大流量為min(5,3)=3。
所以進(jìn)入DFS(T,3)

DFS(T,3):
因?yàn)樽叩絋,所以找到一條增廣路,流量為3,返回增廣流量3上一層。

DFS(3,5):
找到一條增廣路,所以當(dāng)前點(diǎn)3還可以流的流量為:5-3=2(留了一些到T點(diǎn)),在該點(diǎn)所增廣得到的流量為0+3=3
正向邊減3,反向弧加3,也就是:

然后從3尋找下一個(gè)節(jié)點(diǎn)1,由于dis[1]!=dis[3]+1,所以跳過。
接下來沒有其他節(jié)點(diǎn)了,返回該點(diǎn)所增廣流量3到上一層。

DFS(S,∞):
在節(jié)點(diǎn)3得到增廣流量3,所以當(dāng)前節(jié)點(diǎn)可流流量,∞-3=∞(S點(diǎn)可流無窮的水),在S點(diǎn)增廣得到的流量為:0+3=3
同理,正向邊減3,反向弧加3,也就是:

修改完邊之后,遍歷S下一個(gè)節(jié)點(diǎn)1,因?yàn)閐is[1]=dis[S]+1,所以沒有問題,能流進(jìn)1最大流量:min(∞,5)=5,所以進(jìn)入DFS(1,5)

DFS(1,5):
找到下一個(gè)節(jié)點(diǎn)2,dis[2]=dis[1]+1,
能流進(jìn)2最大流量:min(5,6)=5,所以DFS(2,5)

DFS(2,5):
找到下一個(gè)節(jié)點(diǎn)T,dis[T]=dis[2]+1,
能流進(jìn)T最大流量:min(5,10)=5,所以DFS(T,5)

DFS(T,5):
走到T了,直接返回增廣流量5給上一層。

DFS(2,5):
flow=0+5=5,該點(diǎn)還可以流的流量為5-5=0,
正向邊減5,反向邊加5,也就是:

返回5到上一層

DFS(1,5):
flow=0+5=5,還可流流量5-5=0,正向邊減5,反向邊加5,

返回5到上一層

DFS(S,∞):
flow=5+3=8,還可流流量∞,正向邊減5,反向邊加5,

因?yàn)槭窃袋c(diǎn),且此時(shí)已經(jīng)找不到下一個(gè)節(jié)點(diǎn),所以不用回溯,累加器ans加上8。

因?yàn)檎也坏较乱粭l增廣路,所以重新BFS分層,分層后如圖所示:
(剩余容量為0的邊忽略,因?yàn)槠洳荒芡ㄟ^任何流量)

到這里請某位模擬大神上來模擬一下。
模擬后,你們對算法的過程應(yīng)該會有更深刻的了解。

反向邊的存儲和查詢

那么,在實(shí)際編程中,有什么要注意的?
首先,網(wǎng)絡(luò)流的邊可以用鏈?zhǔn)角跋蛐谴鎯?#xff0c;你們應(yīng)該知道,就是:

struct Edge{
int v,w,nxt;
}g[MAXM];
int head[MAXN],cnt;

void addEdge(int u,int v,int w){
g[cnt]=(Edge){v,w,head[u]};
head[u]=cnt;
++cnt;
}

然而反向弧應(yīng)該怎么存儲呢,在dfs過程中addEdge新建邊顯然是不現(xiàn)實(shí)的,
所以可以在建圖過程中加了普通邊后,把反向邊addEdge,容量為0,這樣會方便加。
例如:

addEdge(u,v,w);addEdge(v,u,0);

那么如何求一條邊的反向邊呢,假設(shè)那條邊的編號為i,那么反向邊應(yīng)為i^1,
例如說u-v編號為3,其反向邊v-u編號為3^1=2,
v-u編號為2,其反向邊u-v編號為2^1=3。
但要注意的是邊的編號要從0開始,否則這種方法無效

模版題

只要理解之后,最大流其實(shí)很簡單,其實(shí)還有很多的優(yōu)化,
在這里就由于時(shí)間關(guān)系不講了,可以課后找我問。
同時(shí)也給你們推薦一道模版題 「網(wǎng)絡(luò)流模板」圖圖的最大流
可以拿來測你們模版的時(shí)間(打滿優(yōu)化才能A)

#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <queue> using namespace std; #define inf 0x7fffffffstruct Edge{int v,w,nxt; }g[1001]; int head[1001]; int cnt;void addEdge(int u,int v,int w){g[cnt].v = v;g[cnt].w = w;g[cnt].nxt = head[u];head[u] = cnt;++ cnt; }int n,m,x,y,z; int ans,flow; int dis[1001]; queue<int> q; int S,T;void init(){memset(head,-1,sizeof(head));memset(g,0,sizeof(g));cnt = 0;memset(dis,-1,sizeof(dis));while(!q.empty()) q.pop();ans = 0; }int bfs(){memset(dis,-1,sizeof(dis));while(!q.empty()) q.pop();dis[S] = 0;q.push(S);while(!q.empty()){int u = q.front();q.pop();for(int i=head[u];i!=-1;i=g[i].nxt){int v = g[i].v;if(dis[v]==-1 && g[i].w > 0){dis[v] = dis[u] + 1;q.push(v);}}}return dis[T]!=-1; }int dfs(int u,int exp){if(u==T) return exp;int flow=0,tmp= 0;for(int i=head[u];i!=-1;i=g[i].nxt){int v = g[i].v;if((dis[v] == (dis[u]+1)) && (g[i].w>0)){tmp = dfs(v,min(exp,g[i].w));if(!tmp) continue;exp -= tmp;flow += tmp;g[i].w -= tmp;g[i^1].w += tmp;if(!exp) break;}}return flow; }int main(){while(~scanf("%d%d",&m,&n)){init();S = 1;T = n;for(int i=1;i<=m;++i){scanf("%d%d%d",&x,&y,&z);addEdge(x,y,z);addEdge(y,x,0);}while(bfs()){ans += dfs(S,inf);}printf("%d\n",ans);} }

總結(jié)

以上是生活随笔為你收集整理的网络流(最大流)基础入门的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。