网络流-最大流问题详解(C++实现)
一,算法背景與理論
1.1 前言
1,本文將深入探討最大流問題的dinic算法求解。
2,本文代碼通過C++實現,不涉及算法競賽知識,代碼實現著重于結構化以及功能而非性能。
3,本文關注點在最大流的實現,而其中遇到的其他算法不會深入討論(深度優先搜索DFS,廣度優先搜索BFS)
1.2 基本概念介紹
在最大流問題中,網絡可以看成一個帶權重的有向無環圖。
- 源點(S): 該有向圖中的一個特殊的點,只出不進,被稱作源點。
- 匯點(T): 該有向圖中另一個特殊的點,只進不出,被稱作匯點。
- 容量(maxV): 記錄每條邊最大可通過的流量。
- 流量(flow): 記錄當前邊上通過的流量。
- 最大流(maxFlow): 從源點出發,通過重重有向邊容量的約束,最終能到達匯點的最大流量被稱作最大流。
簡單來說:就好比水廠送水,源點相當于水廠,匯點相當于你家,每個有向邊相當于每條輸水管道,而容量相當于每條輸水管道的最大容納量。于是最大流問題就可以理解成為計算同一時間你家最多能獲得多少水。
1.3 算法步驟詳解
以下圖為例:
其中節點1為源點,節點7為匯點,通過觀察上圖,我們很容易得到該圖最大流是:
1->2->7 flow:22
1->4->7 flow:10
1->5->6->7 flow:45
maxFlow = 77
那么如何通過計算機實現這一過程呢?
了解過算法的都應該知道圖的遍歷可以通過深度優先搜索的方式進行。于是我們就可以很容易的通過深度優先搜索從源點開始向匯點進行搜索,并且在搜索過程中不斷更新邊的權值。為此我們引入殘量的概念。殘量: 記錄當前邊所剩余的容量(即最大容量-當前流量)。
當一條邊的殘量為0,則這條邊可以看作是一條斷邊。
那么如何知曉此時匯點達到了最大流?
可以利用BFS計算每一個節點的深度,我們引入增廣路的概念。
增廣路: 能使匯點流量增大的通路。
于是最大流問題也可以理解為找增廣路的過程,當整個圖中不在存在增廣路的時候也就說明此時匯點流量達到了最大值。
利用BFS標記節點深度,當匯點深度無法被標記的時候,就說明整個圖中再也沒有能到達匯點的通路,也就是沒有增廣路。
但有的時候總會遇到預料之外的情況,如下圖:
通過圖像,我們很清楚的知道若使匯點流量最大,最優通路應該是:
1->2->4 flow:1
1->3->4 flow:1
maxFlow=2
可是假如當出現以下情況時:
此時DFS為我們選擇了1->2->3->4這條路,但顯然不是最優的情況,可是因為是有向圖,搜索無法回退。
如何使算法反悔?
可以通過加反邊的方式實現算法反悔,為每兩個連接的節點之間添加一條殘量為0的反邊。DFS經過時使原始邊減去當前流量的同時為反邊加上當前流量,從而實現反悔。簡單來說就是當水不想從這個管道輸送的時候,這個管道就將把水退回上一個節點。
所以整體算法思路就是BFS確定節點深度,DFS搜索更新殘量并返回此次搜索為匯點增加的流量值,然后繼續BFS確定新的節點深度,再DFS搜索更新殘量并返回此次搜索為匯點增加的流量值,直到BFS發現無法到達源點時,算法停止。
1.4 算法示例
以下圖為例,首先通過BFS確定每一個節點,并規定當前節點只能由depth-1的節點擴展得到,例如depth=2的節點,只能由depth=1的節點擴展得到。這樣做首先是避免了大量不必要的搜索,其次是解決了加反邊后產生的回路造成的死循環的影響。
經過一輪DFS后剩余的邊如下圖所示(這里省略了所有殘量為0的邊):
此時匯點流量為 32:
1->2->7 flow:22
1->4->7 flow:10
再利用BFS計算更新每個節點的深度,然后繼續通過DFS進行搜索,得到1->5->6->7 flow:45,循環往復直到不存在增廣路,最終得到最大流為77。
二,代碼展示
代碼無非是解決以下幾個問題:
1,讀取圖信息,本文是通過讀取txt文本文件來獲取網絡流信息,然后在NetworkFlow.h中確定匯點與源點。
2,創建網絡流圖,本文利用結構體作為節點數據類型,結構體包含3個變量,分別是當前節點深度,當前節點存儲的數據(這個感覺寫的時候有點多此一舉),當前節點包含的邊集,由一個鍵值對<int, int>表示,分別代表指向的節點編號以及邊的最大容量。然后利用map存儲圖所有的節點。
3,為所有節點加反邊。
4,利用BFS計算節點深度。
5,利用DFS找到增廣路,并更新殘量。,在本文的dfs函數中出現了三個變量,flow,rest,delta。其中flow表示當前路線上所能承載的最大流量,rest是當前邊的殘量,delta是該條路上將要更新的流量值。
6,繼續執行4,5兩步直到匯點的深度為-1,即不存在增廣路是停止。
2.1 NetworkFlow.h
#pragma once#include <map>#include <vector>#include <string>#include <algorithm>#include <queue>using namespace std;#define INF 0x3F3F3F // 理論最大值#define T 7 // 匯點#define S 1 // 源點// 定義網絡節點typedef struct node {int data;// 存儲邊集,這里使用set數據結構感覺會更好,加反向邊的時候會方便很多。vector<pair<int, int>> edge;int depth=-1;node(int data, vector<pair<int, int>> edge) {this->data = data;this->edge = edge;}}Node;// 初始化網路map<int, Node> initNetwork();// 格式化文件輸入的數據vector<int> formatInput(string str);// 加反向邊map<int, Node> addReverseEdge(map<int, Node> networkFlow);// 深度優先搜索計算節點深度bool bfs(map<int, Node> &networkFlow);// 深度優先搜索求增廣路int dfs(int currentNode, int flow, map<int, Node> &networkFlow);// dinic算法求最大流int dinic(map<int, Node> networkFlow);// 重置深度void resetting(map<int, Node> &networkFlow);2.2 NetworkFlow.cpp
#include <iostream>#include <fstream>#include "NetworkFlow.h"using namespace std;// 初始化圖結構map<int, Node> initNetwork() {map<int, Node> networkFlow;map<int, Node>::iterator iter;ifstream network("./network.txt");string str;if (!network.is_open()) {cout << "File can't open" << endl;exit(0);}while (getline(network, str)){vector<int> formatNodeInfo = formatInput(str);int prev = formatNodeInfo[0];int next = formatNodeInfo[1];int volume = formatNodeInfo[2];iter = networkFlow.find(prev);if (iter == networkFlow.end()){networkFlow.insert(pair<int, Node>(prev, Node(prev, {})));}iter = networkFlow.find(next);if (iter == networkFlow.end()) {networkFlow.insert(pair<int, Node>(next, Node(next, {})));}iter = networkFlow.find(prev);iter->second.edge.push_back(pair<int, int>(next, volume));}return networkFlow;}// 格式化輸入數據vector<int> formatInput(string str) {vector<int> formatNodeInfo;for (int i = 0; i < str.length(); i++) {string temp = "";while (str[i] != ' ' && i < str.length()) {temp = temp + str[i];i++;}formatNodeInfo.push_back(atoi(temp.c_str()));temp.clear();}return formatNodeInfo;}// 加反向邊map<int, Node> addReverseEdge(map<int, Node> networkFlow) {map<int, Node> resultNetwork = networkFlow;map<int, Node>::iterator iter;for (iter = networkFlow.begin(); iter != networkFlow.end(); iter++) {for (auto item : iter->second.edge) {int next = item.first;int volume = item.second;map<int, Node>::iterator iter_temp;iter_temp = resultNetwork.find(next);if (iter_temp != resultNetwork.end()) {iter_temp->second.edge.push_back(pair<int, int>(iter->first, 0));}else {cout << "添加反向邊錯誤,未找到指向節點" << endl;}}}return resultNetwork;}// 廣度優先搜索求節點深度bool bfs(map<int, Node> &networkFlow) {resetting(networkFlow);queue<int> q;q.push(S);networkFlow.find(S)->second.depth = 0;map<int, Node>::iterator iter;while (!q.empty()) {int currentNode = q.front();q.pop();iter = networkFlow.find(currentNode);if (iter == networkFlow.end()) {cout << "bfs錯誤" << endl;exit(0);}else {for (auto i : iter->second.edge) {int next = i.first;int volume = i.second;map<int, Node>::iterator iter_next = networkFlow.find(next);if (volume != 0 && iter_next->second.depth == -1) {iter_next->second.depth = iter->second.depth + 1;q.push(i.first);}}}}return networkFlow.find(T)->second.depth != -1;}// 深度優先搜索求增廣路int dfs(int currentNode, int flow, map<int, Node> &networkFlow) {if (currentNode == T) {return flow;}int rest = flow;map<int, Node>::iterator iter = networkFlow.find(currentNode);map<int, Node>::iterator iter_next;// 計算當前節點度數int numOfEdge = iter->second.edge.size();for (int i = 0; i < numOfEdge; i++) {int nextNode = iter->second.edge[i].first;int volume = iter->second.edge[i].second;iter_next = networkFlow.find(nextNode);if (iter->second.depth+1 == iter_next->second.depth && volume != 0 && rest != 0) {int delta = dfs(iter_next->first, min(volume, rest), networkFlow);if (!delta) {iter_next->second.depth = 0;}// 更新邊的容量,正邊減,反邊加iter->second.edge[i].second -= delta;vector<pair<int, int>>::iterator iter_ve;for (iter_ve = iter_next->second.edge.begin(); iter_ve != iter_next->second.edge.end(); iter_ve++) {if (iter_ve->first == currentNode) {break;}}iter_ve->second += delta;rest -= delta;}}return flow - rest;}// dinic算法求最大流int dinic(map<int, Node> networkFlow) {int result = 0;while (bfs(networkFlow)) {result += dfs(S, INF, networkFlow);}return result;}// 重置深度void resetting(map<int, Node> &networkFlow) {map<int, Node>::iterator iter;for (iter = networkFlow.begin(); iter != networkFlow.end(); iter++) {iter->second.depth = -1;}}總結
以上是生活随笔為你收集整理的网络流-最大流问题详解(C++实现)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 为什么要用python不用origin_
- 下一篇: Linux格式化异常,Linux下Dat