最大流应用问题(深大算法实验6)报告+代码
目錄
- 寫在前面
- 問題描述
- 圖構建:
- 圖最大流的求解:
- Ford-Fulkerson方法的偽代碼描述:
- Edmonds-karp算法
- 幾個技巧與實現細節:
- Edmonds-karp算法:復雜度分析
- Dinic算法
- Dinic:復雜度分析
- 引入當前弧優化:
- Dinic+當前弧優化:復雜度分析
- 引入多路增廣策略:
- 多路增廣Dinic:復雜度分析
- ISAP
- ISAP復雜度分析
- 預流推進算法
- 預流推進偽代碼描述
- 最高標號預流推進
- 算法測試
- 時間測試
- EK
- Dinic:
- Dinic+當前弧優化
- Dinic+多路增廣
- ISAP
- 最高標號預流推進
- 結論:
- 總覽:
- 數據測試:結論
- 總結:
- 代碼
寫在前面
期末終于算法課快要完結了。
這學期算法課可謂是最難頂的課程了,又正好是線上上課,提問互動的機會相對較少,老師上課拋磚引玉,實驗內容又比較難,我花了大部分的時間在找算法,實現算法,改算法bug上。
我也參考過很多往屆師兄的報告,但是大多都比較抽象晦澀,而且沒有代碼只講方法,比較難以理解具體實現的細節。
所以我打算記錄一下自己的報告+代碼,前人coding后人copying ,希望讓大家少走彎路。。。
注意:不要直接copy代碼,這是沖塔行為!查重系統鯊瘋辣。
問題描述
圖構建:
基本圖
值班問題可以抽象為圖,每個醫生是一個節點,每個假日也是一個節點,醫生和假日之間有路徑表示該醫生可以在這個假日值班
加上虛擬的源點和匯點,組成一張“流網絡”,表示所有值班的可能性
看似求解這個流網絡的最大流就能解決問題,值得注意的是這個網絡并不能很好的描述這個實驗的問題,因為這個實驗有三個限制:
1. 要使得每一個假日都有醫生值班
2. 每個醫生值班不得超過c日
3. 每人每個假期最多值班一天
所以我們的圖要加上相應的限制:引入“限流節點”與“限流邊”
限流節點:對每個醫生的每個假期都虛擬一個限流節點(圖中的x醫生的x假期),而每個醫生最多給每個假期分配1流量,表示每個醫生每個假期只能值班一天
限流邊:我們給每個醫生的流量為c,因為每個醫生最多值班c天,此外,每個假日給到匯點的流量都為1(下圖最右邊黑色邊),表示每個假日一個人值班就夠了,防止出現多人值班同一天的沖突
圖最大流的求解:
Ford-Fulkerson方法與Edmonds-karp算法
回退邊與增廣圖
假設現在有一條邊A->B權值是10,這意味著A可以向B發送10流量
現在A只發送了8流量,那么A還可以發送2流量,此時邊的權值變為2
這時候因為B接收了8流量,B可以選擇退貨,即有回退邊B->A權值為8,表示B可以向A發送8流量,即退貨,我們可以得知,A->B發送x流量,那么A->B的權值-=x,B->A的權值+=x
增廣圖是把所有的回退邊都當成原圖的有向邊,在原圖的基礎上增加回退邊而形成的一種圖
Ford-Fulkerson方法的偽代碼描述:
這里的最短增廣路徑指的是經過的節點最少,而不是邊權值之和最小
Edmonds-karp算法
Ford-Fulkerson方法之所以稱為方法就是因為沒有確定找最短增廣路徑的方法,但是Edmonds-karp算法則實例化了這種方法,即使用bfs求取最短增廣路徑,因為bfs一旦搜索到,那么經過的節點必定是最少的
幾個技巧與實現細節:
Edmonds-karp算法:偽代碼描述
Edmonds-karp算法:復雜度分析
n為頂點數目 e為邊數目
每次bfs需要O(e)的復雜度,而總共需要進行f次bfs圖流量才達到飽和,總共復雜度為O(ef),而f的值為n*e
下面給出證明,f的值為n*e:
每次增廣路徑都會導致一條邊滿載,也就是一條邊被“移除”,但是要想再次經過這條邊,那么必須是從它的反向邊經過了,而從反向邊經過會導致路徑的長度至少增加2,而最短增廣路徑總路程不超過n,所以最多有n/2條經過該邊的路徑,每條邊最多被更新 n/2 次
一共有e條邊,總共的增廣次數就是 n/2 * e 即需要花費 O(ne)次,而每次尋找增廣路徑bfs需要O(e),所以復雜度O(ne2)
Dinic算法
與EK算法類似,dinic算法同樣選擇最短增廣路徑并且更新,不同的是,dinic采用bfs分層dfs的方式,使得一次搜索可以更新多條增廣路上的權值,大大提升速度
Dfs沿著層次進行:
沿著xy方向進行dfs當且僅當x的層次+1 == y的層次,保證dfs找到的都是最短增廣路。
一旦找到終點,路徑即是最短,退棧的同時更新邊的權值即可
Dinic算法:偽代碼描述
代碼使用遞歸定義,分配flow流量給x節點,并且讓x節點嘗試向下分發流量,返回值是x實際分發的流量
dfs遞歸退棧后,重新bfs建立層次關系,然后再次dfs遞歸調用直到bfs無法找到匯點
Dinic:復雜度分析
因為dinic的dfs按照層次來遞歸,因為每次掃描出的層數是遞增的,而且不超過n,所以外循環bfs最多進行n次,而因為每次找增廣路都有一條邊作為“瓶頸”,一共有e條邊,就有e個瓶頸,e條路徑,要進行e次dfs(這里的dfs需要訪問控制數組vis,每個節點最多訪問一次),每次dfs復雜度為O(e),總體復雜度為O(ne2)
引入當前弧優化:
因為dfs一旦找到就返回,而一次bfs分層可能存在很多條相同長度的增廣路,那么會在同一張圖上跑多次dfs,而圖不斷更新,可用的邊越來越少,我們試圖跳過前面已經走過的“死路”,直接從未訪問的路開始走。
當前弧優化:偽代碼實現
實現很簡單,用cur_arc[x]表示x節點未訪問的邊在鄰接表中的起始下標
Dinic+當前弧優化:復雜度分析
因為一共e條邊,而不管做多少次dfs,因為減少了對死路的判斷,保證每次鄰接表第一個鄰居就是能走的活路,所以每次dfs走n步一定能夠走到目標而不必回退,dfs的開銷由O(e)變為O(n),而因為最多有e條路徑,dfs需要最多做e次,所以每一輪的代價是O(ne),而因為外循環的bfs分層最多進行n次,所以總的代價是O(n2e)
Ps. 百度百科上面的Dinic正是用這種當前弧優化 才使得復雜度維持在O(n2e)
引入多路增廣策略:
如下圖所示,普通dinic算法找到之后,直接全部退出,而多路增廣優化的Dinuc算法找到之后,回溯到父節點,然后繼續向下搜索,最后退棧的時候再更新邊的權值,這樣一次dfs可以進行多次更新
多路增廣Dinic:偽代碼描述
和普通dinic算法不同,多路增廣優化的dinic,每次bfs分層后只需要調用一次dfs即可
多路增廣Dinic:復雜度分析
外循環同樣是每次bfs掃描層次,最多做n次bfs(原因同上普通Dinic算法的復雜度分析)
而每次內循環都做一次dfs,需要注意這里的dfs不帶訪問控制數組,只要有邊就能過,這意味著每一個節點可以多次被經過,所以dfs復雜度不是O(e)
因為最多存在e條最短增廣路徑,而每次走到匯點至多走n步,所以dfs的代價是O(ne),這使得多路增廣Dinic的總體復雜度是O(n2e)
ISAP
和Dinic算法類似,同為增廣路算法,不像Dinic算法每次dfs之后都要bfs重新建立層次關系,ISAP在dfs的同時就動態地更新節點的層次關系,減少bfs的次數,將dfs利用到極致
。
ISAP算法只需要一次bfs建立最初的層次關系。除此之外,ISAP算法還引入了“gap”優化,即如果某一高度的節點數目為0,直接判斷沒有路徑并且結束搜索。因為按層次bfs保證層次是嚴格連續下降的,如果某一高度節點數目為0表示出現斷層,無法到達。
我們只需一次bfs,然后一直dfs直到flag為true表示斷層出現,或者源點高度>=n,因為兩者都表示不存在增廣路徑能夠到達匯點
ISAP復雜度分析
和Dinic算法中的dfs一樣,復雜度是O(ne),原因也是不帶訪問控制數組,一個節點會被多次訪問,最多有e條增廣路徑,而每次需要走過n個點到達匯點,所以復雜度O(ne),因為外循環需要判斷源點的高度,而源點最多增高n次,總體復雜度為O(n2e)
預流推進算法
上面提及的算法都是增廣路算法,即按照增廣路徑不斷壓入少量的流量,直到滿流,而預流推進算法則是一次性將巨額流量壓入網絡,如果能夠流就讓他流,即將流量轉到下一個節點,否則就溢出,不管溢出的部分。
超流量與活躍節點
引入超流量概念,這個概念表示某個節點當前狀態下能夠分發出去多少流量,超流量隨著算法的迭代而不停更新。
如果一個節點的超流量大于0,我們稱之為活躍節點,因為他存在分配流量的可能
流量的更新
F單位的流量從x點流向y點,我們稱之為更新流量,即x的超流量-=F 同時y的超流量+=F
標號與重新標號
標號也就是節點的高度(層次),調整一個節點的高度的行為,我們稱之為重新標號,節點的高度調整公式為:level[x] = min(level[x的鄰居])+1
預流推進偽代碼描述
我們用hyper_flow[]數組記錄每個節點的超流量,注意實際代碼中,relabel前還要判斷x是否是匯點,如果是匯點就不管,除此之外relabel如果未發現鄰居就將高度設置為inf,推流時加上訪問控制數組
最高標號預流推進
將普通預流推進算法中的隊列換成依據節點高度排序的優先隊列(堆)即可。
最高標號預流推進:復雜度分析
查閱資料可知該算法的復雜度被證明為O(n2 * sqrt(e))
算法測試
假設有3名醫生,2個假期,每個假日3天假日,每人最多值班2天,按照上文提及的規則,隨機生成的圖如下:
將算法結束后的圖中,有裝載流量的邊都打印出來,得到答案圖:
醫生值班問題的分配方案如下
時間測試
EK
Dinic:
Dinic+當前弧優化
Dinic+多路增廣
ISAP
最高標號預流推進
結論:
可以看到,在四個影響問題的因素中,【醫生個數】【假期個數】【假日個數】對時間復雜度的影響是明顯的,因為他們影響著圖的規模。而【最大值班天數】對問題無影響,曲線隨測試時間誤差與隨機數據而波動。
總覽:
將問題量化為有x個醫生,x個假期,每個假期x個假日,每個醫生隨機選取每個假期的x/2(向上取整)個假日,作為可以值班的假日,生成隨機圖并且計算最大流
對x取不同的值以計算,得出的結果對比如下
可以看到EK算法需要的時間遠超其他算法,我們去除EK算法再次畫圖
可以看到EK算法是最慢的,雖然Dinic算法和EK算法復雜度相同,但是實際隨機數據的測試中效果遠遠好于EK算法。而兩種改進的Dinic算法中,多路增廣Dinic算法比當前弧優化效果要好。最快的算法是ISAP,因為其省去bfs的代價而且有gap優化。而最高標號預流推進算法雖然理論效率最高,但復雜度上限卡的緊,實際效果差強人意。
數據測試:結論
復雜度:
EK = Dinic > 多路增廣Dinic = 當前弧Dinic = ISAP > 最高標號預流推進
實際用時:
EK > Dinic > 當前弧Dinic > 最高標號預流推進 > 多路增廣Dinic > ISAP
總結:
從幾種增廣路徑方法可以看出,圖的遍歷是解決很多圖問題的基礎
從原圖生成增廣圖,其實可以只用生成一次,然后直接在增廣圖上進行操作與修改,不用每次都修改原圖再生成增廣圖
Dfs搜索的時候應當注意鄰接條件,比如邊的權值不能為0或者點是否被訪問,兩種算法使用不同的dfs策略要注意區分
應該使用鄰接表而不是鄰接矩陣存儲,因為鄰接表下的算法時間復雜度低
最高標號預流推進中用到的堆結構,可以直接用STL的優先隊列實現
代碼
#include <bits/stdc++.h>using namespace std;#define inf 1145141919typedef struct edge {int st, ed, val, pair; // 起點, 終點, 還能通過多少流量, 反向邊下標 edge(){}edge(int a, int b, int c, int d){st=a;ed=b;val=c;pair=d;} }edge;int n,e,src,dst,ans; // 頂點數, 初始邊數, 源, 目, 答案 vector<vector<int>> adj; // adj[x][i]表示從x出發的第i條邊在邊集合中的下標 vector<edge> edges; // 邊集合 vector<edge> edges_; // 原始數據 因為每次邊要增廣所以重復計算時要初始化邊 vector<int> min_flow; // min_flow[x]表示從起點到x的路徑中最細流 vector<int> father; // 生成樹 vector<int> level; // 層次 vector<int> cur_arc; // 當前弧優化數組 vector<int> dis_cnt; // 距離計數器 vector<int> hyper_flow; // 超額流量 /* ---------------------------------------------------------------------------- *//** @function bfs_augment : bfs找最短增廣路徑 * @param : ----* @return : 如果找到則return true否則false * @pexlain : father建生成樹 min_flow更新節點最細流 */ bool bfs_augment() {for(int i=0; i<n; i++) father[i]=-1;for(int i=0; i<n; i++) min_flow[i]=inf; // inffather[src] = src;queue<int> q; q.push(src);while(!q.empty()){int x=q.front(),y; q.pop();if(x==dst) return true;for(int i=0; i<adj[x].size(); i++){edge e = edges[adj[x][i]];y = e.ed;if(father[y]!=-1 || e.val==0) continue;father[y] = x;min_flow[y] = min(e.val, min_flow[x]);q.push(y);}}return false; }/** @function graph_update : 根據bfs_augment的生成樹father[]更新圖 * @param : ----* @return : ----* @explain : 需要在bfs_augment之后使用 */ void graph_update() {int x, y=dst, flow=min_flow[dst], i;//cout<<"更新流量: "<<flow<<" 路徑: ";ans += flow;vector<int> path;while(y!=src) // 沿著生成樹找起點并沿途更新邊 {path.push_back(y);x = father[y];for(i=0; i<adj[x].size(); i++) if(edges[adj[x][i]].ed==y) break; edges[adj[x][i]].val -= flow;edges[edges[adj[x][i]].pair].val += flow; // 更新另一半的邊 y = x;}/* path.push_back(y);for(int i=path.size()-1; i>=0; i--) cout<<path[i]<<" "; cout<<endl;*/ }/** @function EK : Edmonds-Karp算法求最大流 */ void EK() {ans = 0;while(1){if(!bfs_augment()) break;graph_update();/* // 打印圖信息for(int i=0; i<edges.size(); i++) cout<<edges[i].st<<" -> "<<edges[i].ed<<" val="<<edges[i].val<<endl;cout<<endl;*/ }//cout<<"最大流:"<<ans<<endl; }/* ---------------------------------------------------------------------------- *//** @function bfs_level : 層次遍歷標記節點層數 * @param : ----* @return ;如果找到終點return true 否則false * @explain : ----*/ bool bfs_level() {for(int i=0; i<n; i++) level[i]=-1;level[src] = 0; // 起始節點第0層 queue<int> q; q.push(src);int lv = 0; // bfs層次數 while(!q.empty()){lv++;int qs = q.size();for(int sq=0; sq<qs; sq++){int x=q.front(),y; q.pop();if(x==dst) return true;for(int i=0; i<adj[x].size(); i++){edge e = edges[adj[x][i]];y = e.ed;if(level[y]!=-1 || e.val==0) continue;level[y] = lv;q.push(y);}}}return false; }/* void dfs_dinic(int x, list<int>& path) {father[x] = 1; // father充當訪問控制數組 for(int i=0; i<adj[x].size(); i++){edge e = edges[adj[x][i]];int y = e.ed;if(father[y]!=-1 || e.val==0 || level[y]!=level[x]+1) continue;path.push_back(adj[x][i]); // path記錄經過的邊的下標 min_flow[y] = min(e.val, min_flow[x]);// 找到則更新所有路徑上的邊 if(y==dst){for(auto it=path.begin(); it!=path.end(); it++)edges[*it].val-=min_flow[y], edges[edges[*it].pair].val+=min_flow[y];ans += min_flow[y];}dfs_dinic(y, path);path.pop_back();} } *//** @function dfs_dinic1 : 普通Dinic算法 往x節點塞入flow流量 并且嘗試分配下去 * @param x : 當前節點 * @param flow : 要向x分配多少流量(最大可用值) * @return : 節點x實際向下分配了多少流量 * @explain : 一旦找到立即返回 一次找一條 * : 使用father數組作為訪問控制visit數組 */ int dfs_dinic1(int x, int flow) {father[x] = 1;if(x==dst) return flow;for(int i=0; i<adj[x].size(); i++){edge e = edges[adj[x][i]];int y = e.ed;if(e.val==0 || level[y]!=level[x]+1 || father[y]==1) continue;int res = dfs_dinic1(y, min(flow, e.val));edges[adj[x][i]].val-=res, edges[edges[adj[x][i]].pair].val+=res;if(res!=0) return res; // 找到直接返回 }return 0; // 沒找到則返回0 } /** @function Dinic1 : 普通Dinic算法求最大流 * @explain : 用level數組做層次標記數組 層次逐漸增加 */ void Dinic1() {ans = 0;while(bfs_level()){while(1){for(int i=0; i<n; i++) father[i]=0;int res = dfs_dinic1(src, inf);if(res==0) break; // 找不到增廣路 需要重新bfs更新層次 ans += res;}/* // 打印圖信息 for(int i=0; i<edges.size(); i++) cout<<edges[i].st<<" -> "<<edges[i].ed<<" val="<<edges[i].val<<endl;cout<<endl;*/ }//cout<<"最大流:"<<ans<<endl; }/** @function dfs_dinic2 : Dinic + 當前弧優化 * @param x : 當前節點 * @param flow : 要向x分配多少流量(最大可用值) * @return : 節點x實際向下分配了多少流量 * @explain : 一旦找到立即返回 一次找一條 * : 使用father數組作為訪問控制visit數組 */ int dfs_dinic2(int x, int flow) {father[x] = 1;if(x==dst) return flow;for(int& i=cur_arc[x]; i<adj[x].size(); i++){edge e = edges[adj[x][i]];int y = e.ed;if(e.val==0 || level[y]!=level[x]+1 || father[y]==1) continue;int res = dfs_dinic2(y, min(flow, e.val));edges[adj[x][i]].val-=res, edges[edges[adj[x][i]].pair].val+=res;if(res!=0) return res; // 找到直接返回 }return 0; // 沒找到則返回0 } /** @function Dinic2 : Dinic + 當前弧優化 * @explain : 用level數組做層次標記數組 層次逐漸增加 */ void Dinic2() {ans = 0;while(bfs_level()){// 當前弧重置 for(int i=0; i<n; i++) cur_arc[i]=0;while(1){for(int i=0; i<n; i++) father[i]=0; // 每次dfs更新visit數組 int res = dfs_dinic2(src, inf);if(res==0) break; // 找不到增廣路 需要重新bfs更新層次 ans += res;}/* // 打印圖信息 for(int i=0; i<edges.size(); i++) cout<<edges[i].st<<" -> "<<edges[i].ed<<" val="<<edges[i].val<<endl;cout<<endl;*/ }//cout<<"最大流:"<<ans<<endl; }/** @function dfs_dinic1 : Dinic + 多路增廣* @param x : 當前節點 * @param flow : 要向x分配多少流量(最大可用值) * @return : 節點x實際向下分配了多少流量 * @explain : 一次找完所有路徑 */ int dfs_dinic3(int x, int flow) {if(x==dst) return flow;int temp_flow = flow; // 記錄能分配的最大值 for(int i=0; i<adj[x].size(); i++){edge e = edges[adj[x][i]];int y = e.ed;if(e.val==0 || level[y]!=level[x]+1) continue;int res = dfs_dinic3(y, min(flow, e.val));edges[adj[x][i]].val-=res, edges[edges[adj[x][i]].pair].val+=res;flow-=res; // 更新可用流量 if(flow==0) return temp_flow; // 如果分完了就結束 }return temp_flow-flow; // 返回實際分配的 } /** @function Dinic1 : Dinic算法 + 多路增廣 * @explain : 用level數組做層次標記數組 層次逐漸增加 */ void Dinic3() {ans = 0;while(bfs_level()){ans += dfs_dinic3(src, inf); // dfs層次圖以更新邊 /* // 打印圖信息 for(int i=0; i<edges.size(); i++) cout<<edges[i].st<<" -> "<<edges[i].ed<<" val="<<edges[i].val<<endl;cout<<endl;*/ }//cout<<"最大流:"<<ans<<endl; }/* ---------------------------------------------------------------------------- *//** @function dfs_ISAP : 在層次圖中向下分配流量 往x節點塞入flow流量 并且嘗試分配下去 * @param x : 當前節點 * @param flow : 要向x分配多少流量(最大可用值) * @return : 節點x實際向下分配了多少流量 * @explain : 和Dinic類似 只是邊bfs邊更新層次 */ bool ISAP_flag = false; int dfs_ISAP(int x, int flow) {if(x==dst) return flow;int temp_flow = flow; // temp_flow保存這個點擁有的流量 for(int i=0; i<adj[x].size(); i++) {edge e = edges[adj[x][i]];int y = e.ed;if(level[x]!=level[y]+1 || e.val==0) continue;int res = dfs_ISAP(y, min(e.val, flow));edges[adj[x][i]].val-=res, edges[edges[adj[x][i]].pair].val+=res; // 退棧時更新圖 flow -= res; // 分配了res流量給某個分支 if(flow==0) return temp_flow; // 分配完了則返回 }dis_cnt[level[x]]--; // 計算新距離計數 if(dis_cnt[level[x]]==0) ISAP_flag=true; // 出現斷層就提前結束 level[x]++; // 已經分配完所子節點 不存在和剛一樣長度增廣路徑 路徑長度嚴格連續增加 dis_cnt[level[x]]++; // 計算新距離計數 return temp_flow-flow; // 返回已經分配的流量數目 }/** @function ISAP : ISAP * @explain : 用level數組做距離標記數組,距離逐漸減少 : 引入當前弧優化和gap優化 */ void ISAP() {bfs_level();// 層次數組變為距離數組 int mlv = *max_element(level.begin(), level.end());for(int i=0; i<n; i++) level[i]=mlv-level[i], dis_cnt[i]=0;// 距離計數數組計算初始值 for(int i=0; i<n; i++) dis_cnt[level[i]]++;ans=0; ISAP_flag=false;while(level[src]<n && !ISAP_flag){ans+=dfs_ISAP(src, inf);if(ISAP_flag) break; } /*// 打印圖信息 for(int i=0; i<edges.size(); i++) cout<<edges[i].st<<" -> "<<edges[i].ed<<" val="<<edges[i].val<<endl;cout<<endl;*/ //cout<<"最大流:"<<ans<<endl; }/* ---------------------------------------------------------------------------- */typedef struct hlpp_node {int x, h;hlpp_node(int a, int b){x=a; h=b;}bool operator < (const hlpp_node& n2)const{return h<n2.h;} }hlpp_node;/** @function relabel : 重新標記高度 * @param x : 重新標記x點的高度 高度為鄰接點之中最低的+1 * @return : ----*/ void relabel(int x) {level[x] = inf;for(int i=0; i<adj[x].size(); i++){if(edges[adj[x][i]].val==0) continue;level[x] = min(level[x], level[edges[adj[x][i]].ed]+1);} }/** @function HLPP : 最高標號預流推進 * @param : ----* @return : ----* @explain : 時間復雜度上界為 O(n^2 * sqrt(e)) 卡的比較緊 */ void HLPP() {bfs_level();// 層次數組變為距離數組 int mlv = *max_element(level.begin(), level.end());for(int i=0; i<n; i++) level[i]=mlv-level[i], dis_cnt[i]=0, hyper_flow[i]=0;vector<int> vis(n);priority_queue<hlpp_node> q; q.push(hlpp_node(src, level[src]));hyper_flow[src] = inf; // 源點無限流量 vis[src] = 1;while(!q.empty()){hlpp_node tp=q.top(); q.pop();int x=tp.x, h=tp.h;if(hyper_flow[x]==0) continue; // 如果流量為0直接出隊 for(int i=0; i<adj[x].size(); i++){edge e = edges[adj[x][i]];int y = e.ed;if(level[x]!=level[y]+1 || e.val==0) continue;int flow = min(hyper_flow[x], e.val);hyper_flow[x]-=flow, hyper_flow[y]+=flow; // 更新超額流量 edges[adj[x][i]].val-=flow, edges[edges[adj[x][i]].pair].val+=flow; // 更新圖if(y!=src && y!=dst && !vis[y]) q.push(hlpp_node(y, level[y])),vis[y]=1;// 不是源目點,則繼續流出 if(hyper_flow[x]==0) break; // 流完了則退出 }// 如果流量有剩余 則抬高 x 以便更多的流出 如果高過源點則回流 不再處理 if(hyper_flow[x]>0 && x!=dst && level[x]<n) {relabel(x); // 抬高 如果無鄰居則高度設為 inf q.push(hlpp_node(x, level[x])); // 試圖再次流出 }}ans = hyper_flow[dst];/*// 打印圖信息 for(int i=0; i<edges.size(); i++) cout<<edges[i].st<<" -> "<<edges[i].ed<<" val="<<edges[i].val<<endl;cout<<endl;*///cout<<"最大流:"<<ans<<endl; }/* ---------------------------------------------------------------------------- *//* * @function add_edge : 添加一條邊及其反向邊* @param st : 正向邊起點* @param ed : 正向邊終點* @param val : 邊的權值(最大允許流量 */ void add_edge(int st, int ed, int val) {int ii=edges_.size();edges_.push_back(edge(st, ed, val, ii+1));edges_.push_back(edge(ed, st, 0, ii));adj[st].push_back(ii); adj[ed].push_back(ii+1); }/* * @function load_random_graph : 隨機生成圖* @param doc_num : 醫生數目 doctor number* @param hol_num : 假期數目 holiday number* @param day_num : 每個假期包含的假日數目 day number* @param c : 每個醫生最多值班c天 */ void load_random_graph(int doc_num, int hol_num, int day_num, int c) {int st, ed;n = 1 + doc_num + doc_num*hol_num + hol_num*day_num + 1;cout<<n<<endl;adj.resize(n);father.resize(n); min_flow.resize(n);level.resize(n); cur_arc.resize(n);dis_cnt.resize(n+1);hyper_flow.resize(n);edges_.clear();edges.clear();src=0, dst=n-1;for(int i=1; i<doc_num+1; i++){st=src, ed=i; add_edge(st, ed, c);}for(int i=1; i<doc_num+1; i++){for(int j=0; j<hol_num; j++){st=i, ed=1+doc_num+doc_num*j+(i-1);add_edge(st, ed, 1);st=ed;vector<int> select(day_num);for(int k=0; k<day_num/2+1; k++){int id = rand()%day_num;// id = k; // 沖突測試用 if(select[id]==1){k--; continue;}select[id] = 1;ed = 1+doc_num+doc_num*hol_num+day_num*j+id;add_edge(st, ed, 1);} }}for(int i=0; i<hol_num*day_num; i++){st=1+doc_num+doc_num*hol_num+i, ed=dst;add_edge(st, ed, 1);}e = edges_.size();edges.resize(e); }/* * @function re_graph : 將圖恢復為默認生成的圖 */ void re_graph() {for(int i=0; i<e; i++) edges[i]=edges_[i]; }void cin_graph() {cin>>n>>e>>src>>dst;adj.resize(n);father.resize(n); min_flow.resize(n);level.resize(n); cur_arc.resize(n);dis_cnt.resize(n+1);hyper_flow.resize(n);for(int i=0; i<e; i++){int st, ed, limit; cin>>st>>ed>>limit;add_edge(st, ed, limit);}e = edges_.size();edges.resize(e); }int main() {//cin_graph();/*re_graph(); // 打印圖信息 for(int i=0; i<edges.size(); i++) cout<<edges[i].st<<" -> "<<edges[i].ed<<" val="<<edges[i].val<<endl;cout<<endl;*/double t1=0, t2=0, t3=0, t4=0, t5=0, t6=0;clock_t st,ed;#define BATCH 10#define SIZE_G 30int batch = BATCH;load_random_graph(SIZE_G, SIZE_G, SIZE_G, SIZE_G);while(batch--){cout<<"數據生成完畢:"<<batch<<endl;re_graph(); st = clock();EK();ed = clock();t1 += (double)(ed-st)/CLOCKS_PER_SEC;re_graph(); st = clock();Dinic1();ed = clock();t2 += (double)(ed-st)/CLOCKS_PER_SEC;re_graph(); st = clock();Dinic2();ed = clock();t3 += (double)(ed-st)/CLOCKS_PER_SEC;re_graph(); st = clock();Dinic3();ed = clock();t4 += (double)(ed-st)/CLOCKS_PER_SEC;re_graph(); st = clock();ISAP();ed = clock();t5 += (double)(ed-st)/CLOCKS_PER_SEC;re_graph(); st = clock();HLPP(); ed = clock();t6 += (double)(ed-st)/CLOCKS_PER_SEC;//re_graph(); EK();//re_graph(); Dinic1(); // 普通dinic //re_graph(); Dinic2(); // Dinic+當前弧優化 //re_graph(); Dinic3(); // Dinic+多路增廣 //re_graph(); ISAP();//re_graph(); HLPP();}cout<<t1/BATCH<<endl;cout<<t2/BATCH<<endl;cout<<t3/BATCH<<endl;cout<<t4/BATCH<<endl;cout<<t5/BATCH<<endl;cout<<t6/BATCH<<endl;return 0; }/* 6 7 0 5 0 2 2 0 1 2 1 4 1 2 4 5 2 3 2 3 5 1 4 5 27 11 0 6 0 1 3 0 3 3 1 2 4 2 0 3 2 3 1 2 4 2 3 4 2 3 5 6 4 1 1 4 6 1 5 6 9*/總結
以上是生活随笔為你收集整理的最大流应用问题(深大算法实验6)报告+代码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: git 忽略 部分文件夹_git提交忽略
- 下一篇: 跳级全奖进哈佛,连马云都忌惮三分,赚18