相關概念:node
給定指定的一個有向圖,其中有兩個特殊的源點S和匯點T,每條邊有指定的容量,求知足條件的從S到T的最大流。ios
殘量網絡=容量網絡-流量網絡
概念就不講了吧,顧名思義。算法
增廣路: 設 f 是一個容量網絡 G 中的一個可行流, P 是從 Vs 到 Vt 的一條鏈, 若 P 知足下列條件:網絡
則稱 P 爲關於可行流 f 的一條增廣路, 簡稱爲 增廣路(或稱爲增廣鏈、可改進路)。沿着增廣路改進可行流的操做稱爲增廣優化
對於一張流量圖G,斷開一些邊後,源點s和匯點t就不在連通,咱們將這樣的k條邊的權值(即最大容量)和求和,求和後的值稱爲割。顯然,對於一張流量圖G,割有不少個且不盡相同。咱們要求的就是全部割中權值最小的那一個(可能不惟一),即花最小的代價使s和t不在同一集合中。spa
FF方法(Ford-Fulkerson)code
根據增廣路定理, 爲了獲得最大流, 能夠從任何一個可行流開始, 沿着增廣路對網絡流進行增廣, 直到網絡中不存在增廣路爲止,這樣的算法稱爲增廣路算法。問題的關鍵在於如何有效地找到增廣路, 並保證算法在有限次增廣後必定終止。
FF方法的基本流程是 :blog
反向弧創建的意義:爲程序提供反悔的機會
很明顯,上圖最大流應該是2,但咱們找到了一條錯誤的路徑,因而咱們就應該有返回的機會,即創建反向邊,這樣再次從反向邊流過就至關於抵消了。隊列
在EK算法中, 程序的實現過程與增廣路求最大流的過程基本一致. 即每一次更新都進行一次找增廣路而後更新路徑上的流量的過程。可是咱們能夠從上圖中發現一個問題, 就是每次找到的增廣路曲曲折折很是長, 此時咱們每每走了冤枉路(即:明明咱們能夠從源點離匯點越走越近的,但是中間的幾條邊卻向離匯點遠的方向走了), 此時更新增廣路的複雜度就會增長。EK 算法爲了規避這個問題使用了 bfs 來尋找增廣路, 而後在尋找增廣路的時候老是向離匯點愈來愈近的方向去尋找下一個結點。博客
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #define INF 0x7fffffff #define N 10010 #define M 100010 using namespace std; int n,m,ss,tt; struct Edge{int to;int next;int value;}e[M<<1]; struct Pre{int node;int id;}pre[M<<1];//pre[i].node表示編號爲i的點最短路的上一個點,pre[i].id表示最短路上鍊接i點的邊的編號 int head[N],cnt=-1;//編號從0開始,緣由見下 bool vis[N]; queue<int> q; void add(int from,int to,int value) { cnt++; e[cnt].to=to; e[cnt].value=value; e[cnt].next=head[from]; head[from]=cnt; } bool bfs(int s,int t)//用來尋找s,t的最短路並記錄,若是s,t不連通則返回0 { q=queue<int>();//清空隊列 memset(vis,0,sizeof(vis)); memset(pre,-1,sizeof(pre)); pre[s].node=s; vis[s]=1; q.push(s); while(!q.empty()) { int x=q.front(); q.pop(); for(int i=head[x];i>-1;i=e[i].next) { int now=e[i].to; if(!vis[now]&&e[i].value)//忽略流量爲0的邊 { pre[now].node=x;//用pre記錄最短路 pre[now].id=i; vis[now]=1; if(now==t)return 1;//找到 q.push(now); } } } return 0; } int EK(int s,int t) { int ans=0; while(bfs(s,t)) { int minv=INF; for(int i=t;i!=s;i=pre[i].node) minv=min(minv,e[pre[i].id].value); for(int i=t;i!=s;i=pre[i].node) { e[pre[i].id].value-=minv; e[pre[i].id^1].value+=minv;//x^1表示x邊的反向邊,此方法僅在邊的編號從0開始時有效 } ans+=minv; } return ans; } int main() { memset(head,-1,sizeof(head)); scanf("%d%d%d%d",&n,&m,&ss,&tt); for(int i=1;i<=m;i++) { int a,b,c; scanf("%d%d%d",&a,&b,&c); add(a,b,c); add(b,a,0);//創建反向邊 } printf("%d\n",EK(ss,tt)); return 0; }
其實Dinic算法是EK算法的改進
發如今EK算法中,每增廣一次都要先進行bfs尋找最短增廣路,然而bfs後,極可能不止一條路徑能夠增廣,若是仍是按照EK算法的bfs一次增廣一條路,很顯然浪費了不少時間,這樣,咱們讓bfs負責尋找增廣路徑,dfs計算可行的最大流。
下圖1點爲s點,6點爲t點,紅線表明尋找的路徑,藍線表明回溯的路徑:
在普通圖中:$\varTheta(n^{2}m)$
在二分圖中:$\varTheta(m\sqrt{n})$
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #define N 10010 #define M 100010 #define INF 0x7fffffff using namespace std; int n,m,ss,tt; int dis[N]; queue<int> q; struct Edge{int to;int value;int next;}e[M<<1]; int head[N],cnt=-1; void add(int from,int to,int value) { cnt++; e[cnt].to=to; e[cnt].value=value; e[cnt].next=head[from]; head[from]=cnt; } bool bfs(int s,int t)//bfs功能和EK算法的類似,不一樣的是Dinic中的bfs要求出全部點到源點s的最短路dis[i] { q=queue<int>();//清空隊列 memset(dis,-1,sizeof(dis)); dis[s]=0; q.push(s); while(!q.empty()) { int x=q.front(); q.pop(); for(int i=head[x];i>-1;i=e[i].next) { int now=e[i].to; if(dis[now]==-1&&e[i].value!=0) { dis[now]=dis[x]+1; q.push(now); } } } return dis[t]!=-1; } int dfs(int x,int t,int maxflow)//表示從x出發尋找到匯點T的增廣路,尋找到maxflow流量爲止,並相應的增廣。返回值爲實際增廣了多少(由於有可能找不到maxflow流量的增廣路) { if(x==t)return maxflow; int ans=0; for(int i=head[x];i>-1;i=e[i].next) { int now=e[i].to; if(dis[now]!=dis[x]+1||e[i].value==0||ans>=maxflow)continue; int f=dfs(now,t,min(e[i].value,maxflow-ans)); e[i].value-=f; e[i^1].value+=f; ans+=f; } return ans; } int Dinic(int s,int t) { int ans=0; while(bfs(s,t)) ans+=dfs(s,t,INF); return ans; } int main() { memset(head,-1,sizeof(head)); scanf("%d%d%d%d",&n,&m,&ss,&tt); for(int i=1;i<=m;i++) { int a,b,c; scanf("%d%d%d",&a,&b,&c); add(a,b,c); add(b,a,0); } printf("%d\n",Dinic(ss,tt)); return 0; }
咱們知道Dinic算法中的dfs是爲了在可行增廣路中找到最小容量並進行增廣。而找增廣路須要遍歷每一個點所鏈接的邊,直至找到一條可到達終點的路。若是這一次找到了增廣路,下一次在訪問到這個點時,上一次已經檢查過的邊就不用再走一遍了,由於遍歷一個點鏈接的邊都是有必定順序的,上一次訪問到這個點已經肯定那幾條邊是不可行的。因而,咱們用cur[i]
來表示下一次遍歷邊時應該從那一條開始。
雖然漸進時間複雜度沒有發生變化,但實際應用中的確大大下降了Dinic的常數
int cur[N]; int dfs(int x,int t,int maxflow) { if(x==t)return maxflow; int ans=0; for(int i=cur[x];i>-1;i=e[i].next) { int now=e[i].to; if(dis[now]!=dis[x]+1||e[i].value==0||ans>=maxflow)continue; cur[x]=i;//此路可行,記錄此路 int f=dfs(now,t,min(e[i].value,maxflow-ans)); e[i].value-=f; e[i^1].value+=f; ans+=f; } return ans; } int Dinic(int s,int t) { int ans=0; while(bfs(s,t)) { memcpy(cur,head,sizeof(head));//初始化 ans+=dfs(s,t,INF); } return ans; }
網絡流的優化算法還有ISAP(Improved Shortest Augumenting Path),最高標號預流推動(HLPP)等等,Dinic在通常狀況下已經夠用了,其餘算法自學請移步其餘大佬博客嘍。