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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

最大流算法

發(fā)布時間:2023/12/20 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 最大流算法 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

目錄

  • 最大流算法
    • 網(wǎng)絡(luò)流基礎(chǔ)概念
      • 網(wǎng)絡(luò)流
      • 可行流
      • 最大流
    • 求解最大流算法
      • 反向邊
      • 增廣路經(jīng)
      • EK算法
      • Dinic算法

最大流算法

網(wǎng)絡(luò)流基礎(chǔ)概念

網(wǎng)絡(luò)流

在一個有向圖G=(V,E)G= (V,E)G=(V,E)中:

  • 有一個唯一的源點S(入度為000:出發(fā)點)
  • 有一個唯一的匯點T(出度為000:結(jié)束點)
  • 圖中的每一條邊都一個非負(fù)的權(quán)值,這個權(quán)值叫做容量c(u,v)c(u, v)c(u,v)

滿足上述條件的圖GGG稱為網(wǎng)絡(luò)流圖,記為G=(V,E,C)G= (V,E,C)G=(V,E,C)

可以想象成,如果把每條邊都看成一個管道,可以看成是水從源點S出發(fā)經(jīng)過這些管道,最終流向匯點T,而每條管道有最大的容量。

例如

可行流

流量:每條弧上給定一個實數(shù)f(u,v)f(u,v)f(u,v),滿足0≤f(u,v)≤c(u,v)0 \leq f(u,v) \leq c(u,v)0f(u,v)c(u,v)

可行流滿足:

  • 源點S:流出量 = 整個網(wǎng)絡(luò)的流量
  • 匯點T:流入量 = 整個網(wǎng)絡(luò)的流量
  • 中間的點:總流入量 = 總流出量,同時0≤f(u,v)≤c(u,v)0 \leq f(u,v) \leq c(u,v)0f(u,v)c(u,v)

這樣的在整個網(wǎng)絡(luò)中的流量就是可行流,可行流有多個

例如

紅色的部分表示流量,黑色的表示容量。可行流就是7

最大流

  • 所有可行流中流量最大的流量
  • 最大流可能不止一個

最大流為11

求解最大流算法

反向邊

  • 首先要明白一點,在一條從SSSTTT的路徑中,能夠帶來的最大流量取決于這條路徑上的最小容量。

對于下面這張圖111是源點,444是匯點

如果我們使用搜索算法找一條從1到4的路徑,并且這條路徑能夠帶來的流量是這條路徑上邊的容量的最小值,

假設(shè)我們找到的路徑是1?>2?>3?>41->2->3->41?>2?>3?>4,現(xiàn)在的流量是111,因為這條路已經(jīng)使用過了,把這條路徑上的每條邊減111,再次找從1到4的路徑,發(fā)現(xiàn)找不到了,因此得到的答案為1,但是正確的答案應(yīng)該是222。即1?>2?>41->2->41?>2?>41?>3?>41->3->41?>3?>4

這個時候,為了能夠繼續(xù)找到路徑,必須要反向建立邊,把當(dāng)前路徑反向建邊,邊的權(quán)值就是這條路徑的流量

如圖

其中紅色的邊就是反向建立的,這個時候繼續(xù)尋找一條路徑,發(fā)現(xiàn)可以走1?>3?>2?>41->3->2->41?>3?>2?>4,帶來的流量是111,然后繼續(xù)找尋路徑,發(fā)現(xiàn)沒有可達的路徑了,因此最大流是1+1=21+1=21+1=2

為什么要反向建邊呢,仔細想想應(yīng)該能夠明白,反向建立邊的作用相當(dāng)于讓之前的路徑有可以反悔的余地。這樣即使一開始走錯也沒有關(guān)系,因為可以通過反向邊來反悔,最終一定能得到正確答案

增廣路經(jīng)

明白了反向建邊后,來看什么是增廣路經(jīng)?

如果一個可行流不是最大流,那么當(dāng)前網(wǎng)絡(luò)中一定存在一條增廣路經(jīng)。

從源點SSS到匯點TTT的一條路徑中,如果邊(u,v)(u,v)(u,v)與該路徑的方向一致就稱為正向邊,否則就記為逆向邊,如果在這條路徑上的所有邊滿足

  • 正向邊f(u,v)&lt;c(u,v)f(u,v) &lt;c(u,v)f(u,v)<c(u,v)
  • 逆向邊f(u,v)&gt;0f(u, v) &gt; 0f(u,v)>0

則該路徑是增廣路徑

其實增廣路徑就是通過這樣一條路徑,來增加到達匯點的流量,而路徑中的流量沒到達容量。

(在上圖中,其實每次找到的一條從111444的路徑都是一條增廣路徑)

沿這條增廣路改進可行流的操作稱為增廣.所有可能的增廣路徑放在一起就構(gòu)成了殘余網(wǎng)絡(luò)

以下這222個算法,都是基于不斷找增廣路經(jīng)來實現(xiàn)的

EK算法

復(fù)雜度O(nm2)O(nm^2)O(nm2)nnn是點數(shù),mmm是邊數(shù)

首先要考慮的是怎么找增廣路徑,之前說用搜索算法,可以用bfsbfsbfs也可以用dfsdfsdfs,但是bfsbfsbfs的好處在于能夠在殘余網(wǎng)絡(luò)中每一次找到最短的一條增廣路徑。因此在EKEKEK算法是基于bfsbfsbfs來找增廣路經(jīng),bfsbfsbfs每執(zhí)行一次,就找出一條增廣路徑來,然后把這條路徑上的權(quán)值進行修改,同時反向建邊。直到找不到增廣路徑為止,算法就結(jié)束了(代碼注釋寫的很詳細)

步驟

  • 利用bfsbfsbfs找到一條最短增廣路徑,記錄該路徑的最小流量
  • 利用一個數(shù)組把這個路徑上的流量更新
  • 不斷重復(fù)1,21,21,2直到?jīng)]有增廣路徑為止

具體實現(xiàn):

POJ 1273(模板題)題目鏈接

代碼

#include <iostream> #include <cstring> #include <cstdio> #include <queue> #define N 205 #define INF 0x7fffffff #define ll long long ll min(ll a, ll b) {return a > b ? b : a; } using namespace std; // Ek 找最大流,其中源點是1,匯點是n ll g[N][N], pre[N], dis[N]; //g用來存圖,pre[i]表示當(dāng)前節(jié)點i的前一個節(jié)點,dis[i]表示從源點到i點的路徑上的最小流量 ll n, m, s, t, ans; queue <ll> q; ll bfs () { //找到一條增廣路經(jīng), 返回這條路徑的流量for(int i = 1; i <= n; i++) pre[i] = -1;while(!q.empty()) q.pop();q.push(s);dis[s] = INF;while (!q.empty()) {ll x = q.front();q.pop();if(x == t) break; //找到了匯點for(int i = 1; i <= n; i++) {if(pre[i] == -1 && g[x][i]) { //找到一個沒有被訪問且還有容量的點pre[i] = x;dis[i] = min(dis[x], g[x][i]); //更新增廣路徑上的最小流量q.push(i);}}}if(pre[t] == -1) return -1; //說明沒有增廣路徑了,因此才走不到匯點else return dis[t]; }void EK () { //更新殘余網(wǎng)絡(luò)的流量ll inc;while (1) {inc = bfs();if(inc == -1) break; //沒有增廣路徑了,算法結(jié)束ll k = t;while (k != s) {g[k][pre[k]] += inc; //建立反向弧g[pre[k]][k] -= inc; //正向容量減去流量k = pre[k];}ans += inc;} }int main () {ll u, v, cost;while(scanf("%lld %lld", &m, &n) != EOF) { //讀入數(shù)據(jù),記得初始化memset(dis, 0, sizeof(dis));memset(g, 0, sizeof(g));ans = 0;s = 1, t = n;for(int i = 1; i <= m; i++) {scanf("%lld %lld %lld", &u, &v, &cost);g[u][v] += cost;}EK();printf("%lld\n", ans);}return 0; }

Dinic算法

復(fù)雜度O(V2E)O(V^2E)O(V2E)

EKEKEK算法找增廣路徑是基于bfsbfsbfs來進行的,bfsbfsbfs會把周圍能夠擴展的點全部擴展進來,直到找到匯點為止,相當(dāng)于每找一次增廣路徑都要搜索大量的點。DinicDinicDinic算法實際上是對EKEKEK的優(yōu)化

DinicDinicDinic算法利用bfsbfsbfs建立分層網(wǎng)絡(luò) (所謂分層網(wǎng)絡(luò),就是按照每一個點到源點的距離,分層,方便后面的dfsdfsdfs)。然后基于這個分層網(wǎng)絡(luò),使用dfsdfsdfs找到當(dāng)前分層網(wǎng)絡(luò)下的所有增廣路徑,并且做好相應(yīng)的修改。然后不斷重復(fù)這兩個過程,直到無法分層為止,這樣做只需要一次bfsbfsbfs就可以找到多條增廣路徑。

(PS:分層網(wǎng)絡(luò),就是利用bfsbfsbfs特性,源點為起點,一直擴散出去,每一個點都打一個標(biāo)記,標(biāo)記到源點的路徑所經(jīng)過的最少的弧的數(shù)量,假設(shè)用dis[i]dis[i]dis[i]表示iii到源點S的最少經(jīng)過的弧的數(shù)量,這樣就可以將整個網(wǎng)絡(luò)分層,基于這個分層網(wǎng)絡(luò),dfsdfsdfs找增廣路徑的時候,就可以找到最短的增廣路徑。如果當(dāng)前點是xxxdfsdfsdfs搜到下一個點iii,一定要滿足dis[i]=dis[x]+1dis[i] = dis[x] +1dis[i]=dis[x]+1,這樣才是最短增廣路徑,效率才是最高的。)

步驟

  • 利用bfsbfsbfs建立分層網(wǎng)絡(luò)(記錄每個節(jié)點的深度)
  • 按照當(dāng)前分層網(wǎng)絡(luò)進行dfsdfsdfs(一層一層找),找到所有該分層網(wǎng)絡(luò)下的增廣路徑,并更新殘余網(wǎng)絡(luò)
  • 重復(fù)1、21、212直到無法建立分層網(wǎng)絡(luò)為止

鄰接矩陣實現(xiàn)

POJ 1273(模板題)題目鏈接

#include <iostream> #include <cstring> #include <cstdio> #include <queue> #define N 205 #define INF 0x7fffffff #define min(a,b) a>b?b:a using namespace std;int n, m, s, t, maxflow; int deep[N], g[N][N]; // deep 表示從源點到當(dāng)前節(jié)點的深度 queue <int> q;bool bfs () { //建立分層網(wǎng)絡(luò)while(!q.empty()) q.pop();memset(deep, -1, sizeof(deep));deep[s] = 0; // 源點的深度為0q.push(s);while(!q.empty()) {int x = q.front();q.pop();for(int i = 1; i <= n; i++) {if(g[x][i] > 0 && deep[i] == -1) {// 如果有容量能夠到達i,并且i節(jié)點的深度未被標(biāo)記deep[i] = deep[x] + 1;q.push(i);}}}if(deep[n] > 0) return true; //能夠建立分層圖else return false; //不存在分層網(wǎng)絡(luò)時,算法結(jié)束 }//基于當(dāng)前分層圖,找到所有增廣路徑 int dfs (int x, int mx) { //表示當(dāng)前節(jié)點x,以及這條增廣路經(jīng)上的最小流量int a;if(x == t) return mx;for(int i = 1; i <= n; i++) {if( g[x][i] > 0 && deep[i] == deep[x] + 1 && (a = dfs(i, min(mx, g[x][i]))) ) {//找到x能流通過去的的相鄰頂點ig[x][i] -= a; //更新殘余網(wǎng)絡(luò)g[i][x] += a;return a;}}return 0; }void dinic () {while(bfs())maxflow += dfs(s, INF); }int main () {int u, v, cost;while(scanf("%d %d", &m, &n) != EOF) {s = 1, t = n;memset(g, 0, sizeof(g));maxflow = 0;for(int i = 1; i <= m; i++) {scanf("%d %d %d", &u, &v, &cost);g[u][v] += cost;}dinic();printf("%d\n", maxflow);}return 0; }

鏈?zhǔn)角跋蛐菍崿F(xiàn)(內(nèi)存小)

其中需要注意的細節(jié):

  • 存邊的時候反邊的容量設(shè)置為0;

  • 假設(shè)dfsdfsdfs在遞歸回來的時候找到了這條增廣路徑的流量為flowflowflow,需要更新正反邊

    • 正邊:edge[i].cap&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;?=&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;flowedge[i].cap \,\,\,\,-= \,\,\,\,flowedge[i].cap?=flow
    • 反邊:edge[i^1].cap&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;+=&ThinSpace;&ThinSpace;&ThinSpace;&ThinSpace;flowedge[i \hat{} 1].cap\,\,\,\, +=\,\,\,\, flowedge[i^1].cap+=flow
    • 其中^\hat{}^表示按位異或,如果iii是偶數(shù)則i^1=i+1i \hat{} 1 = i+1i^1=i+1,如果iii是奇數(shù)則i^1=i?1i \hat{} 1 = i -1i^1=i?1,為什么反邊就是i^1i \hat{} 1i^1呢。因此在存邊的時候,是先存的正邊,然后++cnt++cnt++cnt后存反邊,正反邊差111個,為了保證正邊的編號是偶數(shù),反邊是奇數(shù),cntcntcnt初始化為?1-1?1(因為我的習(xí)慣是寫++cnt++cnt++cnt

洛谷 網(wǎng)絡(luò)最大流(題目鏈接)

#include <cstdio> #include <iostream> #include <cstring> #include <queue> #define INF 0x7fffffff #define N 10001 #define M 100005 using namespace std;struct flow {int to, next, cap; }edge[M * 4];int n, m, s, t, maxflow; int cnt = -1; int head[N], dep[N]; //其中dep[i]表示i的深度,也就是到源點的最小邊數(shù) queue <int> q; inline void addEdge (int u, int v, int cost) {edge[++cnt].to = v;edge[cnt].cap = cost;edge[cnt].next = head[u];head[u] = cnt;//反向建邊,容量為0edge[++cnt].to = u;edge[cnt].cap = 0;edge[cnt].next = head[v];head[v] = cnt; }bool bfs () { //建立分層網(wǎng)絡(luò)while(!q.empty()) q.pop();memset(dep, -1, sizeof(dep));dep[s] = 0;q.push(s);while(!q.empty()) {int x = q.front(); q.pop();for(int i = head[x]; i != -1; i = edge[i].next) {int u = edge[i].to;int cap = edge[i].cap;if(cap > 0 && dep[u] == -1) {dep[u] = dep[x] + 1;q.push(u);}}}if(dep[t] == -1) return 0;else return 1; }int dfs (int x, int mx) { int a;if(x == t) return mx; //找到匯點,返回for(int i = head[x]; i != -1; i = edge[i].next) {int u = edge[i].to;int cap = edge[i].cap;if(cap > 0 && dep[u] == dep[x] + 1 && (a = dfs(u, min(cap, mx))) ) {edge[i].cap -= a; edge[i^1].cap += a; //反向邊return a;}}return 0; //搜不到增廣路經(jīng)了就返回0 } void dinic () {int res;while ( bfs() ) //當(dāng)前網(wǎng)絡(luò)下,搜索所有的增廣路徑while (1) {res = dfs(s, INF); //加上該路徑能帶來的流量if(!res) break;maxflow += res;} }int main () {int u, v, cost;memset(head, -1, sizeof(head));scanf("%d %d %d %d", &n, &m, &s, &t);for(int i = 1; i <= m; i++) {scanf("%d %d %d", &u, &v, &cost);addEdge(u, v, cost);}dinic();printf("%d", maxflow);return 0; }

算法還可以優(yōu)化,加入一個curcurcur數(shù)組。

首先要明白在dfsdfsdfs找增廣路徑的時候,一定是完全增廣的,也就是說這條路徑使用過后,下一次不必再次檢查這條路徑了。直接從下一條邊開使找,加入這個優(yōu)化,算法能快不少

int cur[N]; bool bfs () {while(!q.empty()) q.pop();for(int i = 0; i <= n; i++) cur[i] = head[i]; //復(fù)制head數(shù)組memset(dep, -1, sizeof(dep));dep[s] = 0;q.push(s);while(!q.empty()) {int x = q.front(); q.pop();for(int i = head[x]; i != -1; i = edge[i].next) {int u = edge[i].to;int cap = edge[i].cap;if(cap > 0 && dep[u] == -1) {dep[u] = dep[x] + 1;q.push(u);}}}if(dep[t] == -1) return 0;else return 1; } int dfs (int x, int mx) {int a;if(x == t) return mx;for(int i = cur[x]; i != -1; i = edge[i].next) {cur[x] = i; //避免了搜尋已經(jīng)增廣過的路徑int u = edge[i].to;int cap = edge[i].cap;if(cap > 0 && dep[u] == dep[x] + 1 && (a = dfs(u, min(cap, mx))) ) {edge[i].cap -= a;edge[i^1].cap += a; //反向邊return a;}}return 0; //搜不到增廣路經(jīng)了就返回0 }

總結(jié)

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

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