网络流算法整理

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;
	}
}


 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值