(轉自http://blog.csdn.net/wangjian8006/article/details/7939599)
EK(EdmondsKarp)算法:這個算法改進於Ford-Fulkerson算法,Ford-Fulkerson算法是不斷用廣搜找一條增廣路,然後判斷一次這條路的最小流量,再對這條路增流,而EK與FF算法不同的是用一個數組記錄了廣搜之後增廣路的最小流量,然後再根據父親數組去增流,時間複雜度爲O(VE^2)
- typedef struct{
- int flow; //流量
- int capacity; //最大容量值
- }maps;
- maps map[MAXV][MAXV];
- int maxflow; //最大流
- int sp,fp; //標記源點與匯點
- int parent[MAXV]; //用於bfs尋找路徑
- int vertime; //頂點總數
- int bfs(int start,int end){
- int a[MAXV],i,v;
- queue <int>q;
- memset(a,0,sizeof(a)); //記錄增廣路最小流量,而且又可以當做廣搜的標記數組
- memset(parent,-1,sizeof(parent)); //記錄下這條增廣路,以便增流
- q.push(start);
- a[start]=MAXINT;
- while(!q.empty()){
- v=q.front();q.pop();
- for(i=1;i<=vertime;i++){
- if(!a[i] && map[v][i].capacity>map[v][i].flow){ //如果這是一條允許弧就記錄下來
- q.push(i);
- parent[i]=v;
- a[i]=min(a[v],map[v][i].capacity-map[v][i].flow);
- }
- }
- if(v==end) break; //找到增廣路退出
- }
- return a[end];
- }
- void EdmondsKarp(){
- int i,tmp;
- maxflow=0;
- while(tmp=bfs(sp,fp)){
- for(i=fp;i!=sp;i=parent[i]){ //根據父親數組更新流量
- map[i][parent[i]].flow-=tmp; //更新反向流
- map[parent[i]][i].flow+=tmp; //更新正向流
- }
- maxflow+=tmp;
- }
- }
SAP(最短增廣路):先簡單的描述一下SAP的過程:
首先根據可行弧(容量>流量)分層,匯點爲第0層,源點爲d[s]層
........①
分層之後,不斷從源點搜索增廣路,按允許弧(容量>流量,並且由v指向u的一條弧滿足d[v]=d[u]+1)搜索,也就是說前面的層數比後面層數
小1,這樣搜索出來的一條增廣路滿足最短增廣路。
........②
當從源點出發找不到一條增廣路之後,使最後那條可行流的所指的頂點層數加1,再從源點開始搜索,即進行第②步操作。
........③
當從源點出發找到一條增廣路,對這條增廣路增流之後,再從源點出發找增廣路,即進行第2步操作。
........④
當滿足d[s]>n-1則退出搜索,因爲圖爲n個節點,從0開始最多n-1層,如果d[s]大於n-1層,則中間出現斷層,就找不到那麼一條增廣路了。
........⑤
這樣分析這個算法的複雜度,網絡中最多m條邊,做多可以增廣m次,用BFS增廣,一次增廣的複雜度爲O(m+n),其中O(m)爲BFS的花費,O(n)
爲修改流量的花費,所以在每一個階段尋找增廣路的複雜度爲O(m×(m+n))=O(m^2),因此n個階段尋增廣路的複雜度爲O(n×m^2).
綜上所述,最短增廣路算法的總複雜度即爲建層次網絡的總複雜度與尋找增廣路的總複雜度之和,爲O(n×m^2)
- int n; //點的總數
- int c[MAXV][MAXV]; //容量
- int r[MAXV][MAXV]; //殘量
- int source,sink; //源點與匯點
- int dis[MAXV],maxflow; //分層數組與最大流
- void bfs(){
- int v,i;
- queue <int>q;
- memset(dis,0,sizeof(dis));
- q.push(sink); //匯點爲0層
- while(!q.empty()){
- v=q.front();q.pop();
- for(i=0;i<=sink;i++){
- if(!dis[i] && c[i][v]>0){
- dis[i] = dis[v] +1;
- q.push(i);
- }
- }
- }
- }
- void sap(){
- int top=source,pre[MAXV],i,j,low[MAXV];
- bfs(); //分層
- memset(low,0,sizeof(low)); //保存路徑的最小流量
- while(dis[source]<n){
- low[source]=INF;
- for(i=0;i<=sink;i++){ //找到一條允許弧
- if(r[top][i]>0 && dis[top]==dis[i] +1) break;
- }
- if(i<=sink){ //找到了允許弧
- low[i]=min(r[top][i],low[top]); //更新當前的最小流量
- pre[i]=top;top=i; //記錄增廣路徑
- if(top==sink){ //找到一條增廣路徑更新殘量
- maxflow+=low[sink];
- j=top;
- while(j != source){
- i=pre[j];
- r[i][j]-=low[sink];
- r[j][i]+=low[sink];
- j=i;
- }
- top=source; //再從頭找一條增廣路徑
- }
- }
- else{ //找不到這樣一條允許弧更新距離數組
- int mindis=INF;
- for(j=0;j <=sink;j++){
- if(r[top][j]>0 && mindis>dis[j] +1)
- mindis=dis[j] +1;
- }
- dis[top]=mindis; //更新最後那個阻塞流節點的層數
- if(top!=source) top=pre[top];
- }
- }
- }
運用gap優化:
即當標號中出現了不連續標號的情況時,即可以證明已經不存在新的增廣流,此時的流量即爲最大流。
簡單證明下:
假設不存在標號爲k的結點,那麼這時候可以將所有的結點分成兩部分,一部分爲d(i)>k,另一部分爲d(i)<k
如此分成兩份,因爲性質2可知,允許路爲最短的增廣路,又因爲不存在從>k部分到<k部分的增廣流,那麼有最大流最小割定理可知此時便是
最大流。
sap+gap+鄰接矩陣
- #define INF INT_MAX
- #define min(a,b) (a>b?b:a)
- //n爲總點數
- int sink,source,res[MAXV][MAXV],n;
- int pre[MAXV],dis[MAXV],gap[MAXV],maxflow,cur[MAXV];
- int sap(){
- int s=source,t=sink;
- memset(cur,0,sizeof(cur));
- memset(dis,0,sizeof(dis));
- memset(gap,0,sizeof(gap));
- int u=pre[s]=s,aug=INF,v;
- maxflow=0;
- gap[source]=n;
- while(dis[s]<n){
- loop:
- for(v=cur[u];v<n;v++)
- if(res[u][v] && dis[u]==dis[v]+1){
- cur[u]=v;
- aug=min(aug,res[u][v]);
- pre[v]=u;
- u=v;
- if(v==t){
- maxflow+=aug;
- for(u=pre[u];v!=s;v=u,u=pre[u]) res[u][v]-=aug,res[v][u]+=aug;
- aug=INF;
- }
- goto loop;
- }
- int mind=n;
- for(v=0;v<n;v++)
- if(res[u][v]&&(mind>dis[v])){
- cur[u]=v;
- mind=dis[v];
- }
- if((--gap[dis[u]])==0) break;
- gap[dis[u]=mind+1]++;
- u=pre[u];
- }
- return maxflow;
- }
sap+gap+鄰接表
- typedef struct{
- int t,r,contrary,next;
- }Edge;
- Edge edge[MAXE];
- int n,m,source,sink,edge_sum,maxflow;
- int head[MAXV],dis[MAXV],cur[MAXV],gap[MAXV],pre[MAXV];
- void sap(){
- int u=pre[source]=source,tmp=INF,v,a;
- memset(dis,0,sizeof(dis));
- memset(gap,0,sizeof(gap));
- for(v=0;v<=n;v++) cur[v]=head[v];
- gap[source]=n;
- maxflow=0;
- while(dis[source]<n){
- loop:
- for(v=cur[u];v!=-1;v=edge[v].next){
- a=edge[v].t;
- if(dis[u]==dis[a]+1 && edge[v].r>0){
- cur[u]=v;
- tmp=min(tmp,edge[v].r);
- pre[a]=u;
- u=a;
- if(u==sink){
- while(u!=source){
- u=pre[u];
- edge[cur[u]].r-=tmp;
- edge[cur[u]^1].r+=tmp;
- }
- maxflow+=tmp;
- tmp=INF;
- }
- goto loop;
- }
- }
- int mind=n;
- for(v=head[u];v!=-1;v=edge[v].next){
- a=edge[v].t;
- if(edge[v].r>0 && mind>dis[a]){
- cur[u]=v;
- mind=dis[a];
- }
- }
- if((--gap[dis[u]])==0) break;
- gap[dis[u]=mind+1]++;
- u=pre[u];
- }
- }
dinic算法(連續最短增廣路算法):
首先dinic也用到了分層的思想,但是它與最短增廣路不同的是:
SAP在每個階段執行完一次BFS增廣後,要重新啓動BFS從源點到匯點找一條增廣路
Dinic算法,只要一次DFS過程就能找出多條增廣路,進行增廣
下面簡單描述下Dinic算法的過程:
首先分層,源點的層次爲第0層,而匯點的層次爲第d[t]層。
........①
分層之後,進行深搜,深搜的時候也是按允許弧深搜(容量>流量,並且由v指向u的一條弧滿足d[v]=d[u]+1),在搜索的過程中會記錄下當前的
最小流量和當前節點剩餘流量,這樣在回溯的時候就可以不斷的找多條增廣路並進行更新,因爲是按照允許弧進行尋找增廣路,所以也會滿
足找的增廣路是最短的。
........②
當深搜完畢之後,再次進行分層,即進行第①步
........③
分析dinic的複雜度,在每一個階段,DFS遍歷時前進與後退的花費爲O(m×n),因爲最多進行n次DFS,所以在Dinic算法中找增廣路的總複雜度
爲O(m×n^2)
- #define MAXV 410
- #define INF INT_MAX
- #define min(a,b) (a>b?b:a)
- int res[MAXV][MAXV]; //殘量
- int dis[MAXV]; //表示多少層
- int source,sink,n,maxflow; //s爲源點,t爲匯點
- int bfs(){
- int k;
- queue<int> q;
- memset(dis,-1,sizeof(dis));
- dis[sink]=0;
- q.push(sink);
- while(!q.empty()){
- k=q.front();
- q.pop();
- for(int i=0;i<n;i++){
- if(dis[i]==-1 && res[i][k]){
- dis[i] = dis[k] + 1;
- q.push(i);
- }
- }
- if(k==source) return 1;
- }
- return 0;
- }
- int dfs(int cur,int cp){
- if(cur==sink) return cp;
- int tmp=cp,t;
- for(int i=0;i<n && tmp;i++){
- if(dis[i]+1==dis[cur] && res[cur][i]){
- t=dfs(i,min(res[cur][i],tmp));
- res[cur][i]-=t;
- res[i][cur]+=t;
- tmp-=t;
- }
- }
- return cp-tmp;
- }
- void dinic(){
- maxflow=0;
- while(bfs()) maxflow+=dfs(source,INF);
- }
鄰接表:鄰接表:鄰接表:鄰接表:鄰接表:鄰接表:
鄰接表:
- typedef struct{
- int s,t,r,next;
- }Edge;
- Edge edge[MAXE];
- int n,m,source,sink,edge_sum,maxflow;
- int head[MAXV],dis[MAXV];
- int bfs(){
- int i,v,tmp;
- queue <int>q;
- memset(dis,0,sizeof(dis));
- dis[source]=1;
- q.push(source);
- while(!q.empty()){
- v=q.front();q.pop();
- for(i=head[v];i!=-1;i=edge[i].next){
- tmp=edge[i].t;
- if(!dis[tmp] && edge[i].r){
- dis[tmp]=dis[v]+1;
- if(tmp==sink) return 1;
- q.push(tmp);
- }
- }
- }
- return 0;
- }
- int dfs(int cur,int cp){
- if(cur==sink) return cp;
- int tmp=0,i,a,t;
- for(i=head[cur];i!=-1 && tmp<cp;i=edge[i].next){
- a=edge[i].t;
- if(dis[a]==dis[cur]+1 && edge[i].r){
- t=dfs(a,min(edge[i].r,cp-tmp));
- edge[i].r-=t;
- edge[i^1].r+=t; //反邊減殘量
- tmp+=t;
- }
- }
- if (!tmp) dis[cur]=-1; //這裏代表流已經沒了,或者說此路不通
- return tmp;
- }
- void dinic(){
- maxflow=0;
- while(bfs()) maxflow+=dfs(source,INF);
- }
相對來說,EK算法和dinic算法容易實現和調試,並且dinic算法的效率也很高,所以大多數人都選擇了dinic算法,但是sap的gap優化其實效
率是大於dinic的,所以有的時候卡時間可以選擇sap+gap優化
最大費用最大流:
最大流問題僅注意網絡流的流通能力,沒有考慮流通的費用。實際上費用因素是很重要的。例如在交通運輸問題中,往往要求在完成運輸任
務的前提下,尋求一個使總運輸費用最省的運輸方案,這就是最小費用流問題。如果只考慮單位貨物的運輸費用,那麼這個問題就變成最短
路徑問題,由此可見,最短路問題是最小費用流問題的基礎。現已有一系列求最短路的成功方法。 最小費用流(或最小費用最大流)問題,可
以交替使用求解最大流和最短路兩種方法,通過迭代得到解決。
一般講費用看成是求最小路徑的模型,在求最短路的時候限制一下條件就可以了。
MCMF(spfa)鄰接矩陣:
- int source,sink,maxflow,mincost;
- int res[MAXV][MAXV],cost[MAXV][MAXV];
- int parent[MAXV],d[MAXV];
- void spfa(){
- queue <int>q;
- int i,v;
- bool vis[MAXV];
- memset(parent,-1,sizeof(parent));
- memset(vis,false,sizeof(vis));
- for(i=source;i<=sink;i++) d[i]=INF;
- d[source]=0;
- q.push(source);
- vis[source]=true;
- while(!q.empty()){
- v=q.front();q.pop();
- vis[v]=false;
- for(i=0;i<=sink;i++){
- if(res[v][i] && d[v]+cost[v][i]<d[i]){
- d[i]=d[v]+cost[v][i];
- parent[i]=v;
- if(!vis[i]){
- vis[i]=true;
- q.push(i);
- }
- }
- }
- }
- }
- void MCMF(){
- int v,minflow;
- maxflow=0; //總的最大流
- while(1){
- spfa();
- if(parent[sink]==-1) break; //搜不到最短路了就退出
- minflow=INF; //找出最短路徑的最小增廣流
- v=sink;
- while(parent[v]!=-1){
- minflow=min(minflow,res[parent[v]][v]);
- v=parent[v];
- }
- v=sink; //對增廣路進行流增廣
- while(parent[v]!=-1){
- res[parent[v]][v]-=minflow;
- res[v][parent[v]]+=minflow;
- v=parent[v];
- }
- maxflow+=minflow;
- mincost+=d[sink]*minflow; //總的代價
- }
- }
MCMF(spfa)鄰接表:
- #define INF INT_MAX
- #define min(a,b) (a>b?b:a)
- #define MAXV 1100
- #define MAXM 40100
- typedef struct{
- int s,t,next,w,r;
- }Edge;
- Edge edge[MAXM];
- int source,sink,n,m,mincost,edgesum;
- int head[MAXV],d[MAXV],parent[MAXV];
- void addedge(int a,int b,int c,int r){ //無向邊要求兩次,一條無向邊對應4條這樣的邊
- edge[edgesum].s=a;
- edge[edgesum].t=b;
- edge[edgesum].r=r;
- edge[edgesum].w=c;
- edge[edgesum].next=head[a];
- head[a]=edgesum++;
- edge[edgesum].s=b;
- edge[edgesum].t=a;
- edge[edgesum].r=0;
- edge[edgesum].w=-c;
- edge[edgesum].next=head[b];
- head[b]=edgesum++;
- }
- int spfa(){
- queue <int>q;
- int v,i,tmp;
- bool vis[MAXV];
- for(i=0;i<=sink;i++) d[i]=INF;
- memset(vis,false,sizeof(vis));
- memset(parent,-1,sizeof(parent));
-
- q.push(source);
- vis[source]=true;
- d[source]=0;
- while(!q.empty()){
- v=q.front();q.pop();
- vis[v]=false;
- for(i=head[v];i!=-1;i=edge[i].next){
- tmp=edge[i].t;
- if(edge[i].r && edge[i].w+d[v]<d[tmp]){
- d[tmp]=edge[i].w+d[v];
- parent[tmp]=i;
- if(!vis[tmp]){
- q.push(tmp);
- vis[tmp]=true;
- }
- }
- }
- }
- return 0;
- }
- void MCMF(){
- int u,minflow;
- mincost=0;
- while(1){
- spfa();
- if(parent[sink]==-1) break;
- minflow=INF;
- u=parent[sink];
- while(u!=-1){
- minflow=min(minflow,edge[u].r);
- u=parent[edge[u].s];
- }
- u=parent[sink];
- while(u!=-1){
- edge[u].r-=minflow;
- edge[u^1].r+=minflow;
- u=parent[edge[u].s];
- }
- mincost+=d[sink]*minflow;
- maxflow+=minflow;
- }
- }