高階篇——Dinic最大流演算法
阿新 • • 發佈:2019-01-09
起於源點s,止於匯點t,解決最大流問題的過程就是不斷尋找增廣路徑的過程。最大流問題專用術語先一一解釋。
1.增廣路徑:從源點到匯點不一定只有一條路,要想做到流到匯點的流量最大,必須使得每一條能到匯點的路徑都能被流經。每一條從源點到匯點的路徑便是一條增廣路徑。
2.反向弧:每從一點到達另一點,都需要在相反的方向上設定一條反向邊,每條反向邊的作用可以理解為給演算法一個可以反悔的機會。
3.殘餘網路:當每從一點到達其下一個點時,這個點可以經過的流量需要減去實際經過的流量,以便往後尋找增廣路徑時不會出現流量大於可行流量的情況。這樣處理過的流量網路便是殘餘網路。
最大流問題較為高效的方法是Dinic和SAP,Dinic方法結合了中級篇講的BFS和DFS演算法的優勢,通過BFS演算法尋找增廣路徑,DFS演算法求得每條可行流的最大值。
先來講講如何尋找增廣路徑。
增廣路徑用BFS演算法。設定陣列dis[ ],表示每個點距離源點的距離。初始化為-1,源點dis[ 1 ] =1,之後開始遍歷,一要是能到達且該點對應dis值為-1,便將該點dis值設為前點dis值加1。這個有什麼用?等到了DFS的時候就知道了 。如果最終匯點的dis值不為-1,及說明會點可以到達,增廣路徑存在,返回true,否則返回false。
int BFS() { queue<int>que; memset(dis,-1,sizeof(dis));//每次尋找增廣路徑都需要將dis陣列重置 dis[1]=0; //起始點為0 que.push(1); while(!que.empty()) { int cur=que.front(); que.pop(); for(int i=1;i<=n;i++) { if(c[cur][i]&&dis[i]<0) //如果可行且該點尚未被標註 { dis[i]=dis[cur]+1; //改變dis值 que.push(i); //入隊繼續遍歷 } } } if(dis[n]>0) return true; //匯點值不為-1,說明增廣路徑存在 return false; //反之不存在 }
BFS負責尋找增廣路徑,DFS計算可行的最大流,此時dis[ ]就派上用場了。一旦經過BFS發現存在可行流,現在要做的便是從源點出發,只要遇到可以到達且dis[ 終點 ]=dis[ 起點 ]+1便繼續對該終點進行DFS搜尋。也就是說DFS是一個不斷遞迴的過程。當終點為匯點時返回對應的流量值,並構建殘餘網路和反向弧。
int DFS(int point,int MAX) //point為當前遍歷的節點,MAX為最大可行流量,MAX初始為INF { int a; if(point==n) return MAX; //當前點為匯點時返回即可 for(int i=1;i<=n;i++) { //當路徑可行,對終點進行DFS if(c[point][i]&&dis[i]==dis[point]+1&&(a=DFS(i,min(MAX,c[point][i])))) { c[point][i]-=a; //構建殘餘網路 c[i][point]+=a; //建立反向弧 return a; } } return 0; }
以POJ 1273為例:http://poj.org/problem?id=1273
題意:m條路徑,n個點,每條路徑輸入起點,終點和流量,求源點到匯點的最大流量
分析:這題直接套模板就行
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <queue>
#define INF 9999999
using namespace std;
int n,m;
int c[205][205],dis[205]; //c陣列為原始網路
int BFS()
{
queue<int>que;
memset(dis,-1,sizeof(dis));//每次尋找增廣路徑都需要將dis陣列重置
dis[1]=0; //起始點為0
que.push(1);
while(!que.empty())
{
int cur=que.front();
que.pop();
for(int i=1;i<=n;i++)
{
if(c[cur][i]&&dis[i]<0) //如果可行且該點尚未被標註
{
dis[i]=dis[cur]+1; //改變dis值
que.push(i); //入隊繼續遍歷
}
}
}
if(dis[n]>0) return true; //匯點值不為-1,說明增廣路徑存在
return false; //反之不存在
}
int DFS(int point,int MAX) //point為當前遍歷的節點,MAX為最大可行流量,MAX初始為INF
{
int a;
if(point==n) return MAX; //當前點為匯點時返回即可
for(int i=1;i<=n;i++)
{
//當路徑可行,對終點進行DFS
if(c[point][i]&&dis[i]==dis[point]+1&&(a=DFS(i,min(MAX,c[point][i]))))
{
c[point][i]-=a; //構建殘餘網路
c[i][point]+=a; //建立反向弧
return a;
}
}
return 0;
}
int main()
{
while(cin>>m>>n)
{
memset(c,0,sizeof(c));
while(m--) //構建原始網路
{
int u,v,w;
cin>>u>>v>>w;
c[u][v]+=w;
}
int sum=0;
while(BFS()) //當增廣路徑存在進行DFS
{
int s=DFS(1,INF); //從源點出發,初始最大可行流設為無窮大
sum+=s; //結果加上得到的最大可行流
}
cout<<sum<<endl;
}
return 0;
}