利用图型数据关系解题之一网络流

算法适用问题:网络流是一种重要的组合优化问题,在现实中很多问题都可以看作是网络流问题,例如运输货物,水流问题,匹配问题,网络是一种每个边都有方向并且带权的图,一般情况下我们会把各种网络问题抽象成网络流问题,方便解题。
网络流是满足以下性质的网络:每一条边拥有一个最大的容量c,即该条路可以容纳的最大流量(这个流量可以理解成速度,而不是一共能流过多少),f是流过该边的实际流量,且总有f<=c。对于图中每个顶点(源点和汇点除外)都有流出的流量等于流入的流量。图中有两个特殊点,一个源点和一个汇点,源点是起始点,汇点是终点,且对于源点来说其流入量为0,对于汇点来说流出量为0,源点的流出量等于汇点的流入量,对于最大流问题既是要找出流入汇点的最大流量值 。打个比方,一座城市只有一个自来水场,却有很多用户,一些管道(有粗有细)将自来水场和用户连接起来,(如果一个用户1和自来水场连接,用户2和用户1连接,那么用户2也可以认为和自来水厂连接),自来水厂就可以视为源点,其中某一个用户则可以视为汇点,如果自来水场不断加大单位时间注水的量,则用户单位时间内能接受的最大水流量就是网络的最大流。
定义
网络:有一个源点 s 和汇点 t ,每一条有向边e=(u,v)都有一个容量限制记做c(e)。
:定义在网络弧集上的实值函数 f ,满足三个性质
(1)对任意的弧 0 <= f <= c(e),容量限制。
(2)f(u,v) == -f(v,u),反对称性。
(3)流守恒性:除源汇点外,其余顶点都是过度点,流进顶点的流总和等于流出顶点的流总和。
残余网络:用e表示网络中的边,e’ 表示残余网络中的边,残余网络中的边由以下两种构成:
1)若f(e) < c(e) ,e=(u,v),则e’ =(u,v)容量为 c(e) - f(e),表示生成的边表示沿着这条边还能推进多少流
2)若f(e)>0,e=(u,v),则加入边 e’=(v,u),容量为 f(e),表示生成的边表示沿着该边的逆方向能退回多少流。
增广路: 定义增广路 P 是在残余网络上的一条从源点 s 到汇点 t 的简单路径,路径的残余流量为该边上的边 e’ 容量的最小值,其实就是残余网络上增广的流值大于 0 的一条路径。
割的定义:设网络G,如果 X 是 V 的顶点子集,Y 是 X 的补集,即 Y = V - X,且满足 源点 属于X,汇点 属于 Y。则称K=(X,Y)为网络 G 的割,K的容量记为 cap(K) 最小割就是该网络中流量最小的割。
最小割最大流定理:指在一个网络流中,能够从源点到达汇点的最大流量 等于 如果从网络中移除就能够导致网络流中断的边的集合的最小容量和。即在任何网络中,最大流的值等于最小割的容量。
求解最大流的算法
*Dinic算法
*Edmonds-Karp算法
*Ford-Fulkerson方法
*sap算法
一.Dinic算法
算法步骤:
(1)初始化容量网络和网络流。
(2)构造残留网络和层次网络,若汇点不再层次网络中,则算法结束。
(3)在层次网络中用一次DFS过程进行增广,DFS执行完毕,该阶段的增广也执行完毕。
(4)跳到步骤(2)。
算法结束后,最大网络流求解完毕。
算法最重要的两步即(1)构造残余网络和层次网络。(2)在层次网络中使用DFS进行增广。
定义存储网络的存储结构

class Edge{
	public:
	int c,f;//c表示正向流量,f表示反向流量
};
Edge edge[max_V][max_V]//max_V表示节点个数
int level[max_V]//level存储节点的层次

1)构造残余网络和层次网络
使用BFS建立层次网络

bool dinic_bfs(int s,int t){//s为源点,t为汇点构建
	memset(level,0,sizeof(level));//将层次网络初始化
	queue<int>q;//定义队列,进行BFS
	q.push(s);//将s推入队列
	level[s]=1;//将源点的level置为1
	while(!q.empty()){//开始BFS
		int tmp=q.front();
		q.pop();
		for(int i=1;i<=N;i++){//搜索其余定点
			if(!level[i]&&edge[tmp][i].c>edge[tmp][i].f){
				//如果i点为进入层次网络,并且从tmp点到i点还有流量,
				//则将i点划入层次网络,并且其层次是tmp节点的层次加一
				level[i]=level[tmp]+1;
				q.push(i);//将i点推如队列
			}
		}
	}
	return level[t]!=0;//返回汇点是否在当前构造的层次网络中
}

2)在层次网络中进行增广。
使用DFS进行寻找增广路

int dinic_dfs(int u,int t,int cp){//u是进行寻路的起点,t是寻路的终点,(就是汇点)
	int tmp=cp;
	if(u==t){
		return cp;//如果已找到,则返回inf
	}
	for(int i=1;i<=N;i++){
		if(level[u]+1==level[i]){//如果是下一个层次,则判断其是否有流量
			if(edge[u][i].c>edge[u][i].f){
				int x=dinic_dfs(i,t,min(tmp,edge[u][i].c-edge[u][i].f));
				//在当前的基础上继续进行DFS寻路
				edge[u][i].f+=x;//将从u到i的反流加上x
				edge[i][u].f-=x;//将从i到u的反流减去x
				tmp-=x;
			}
		}
	}
	return cp-tmp;//返回当前增广路的最大流量
}

Dinic算法核心

int dinic(int s,int t){//s为源点,t为汇点
	int ans=0,tp;//ans为最大流
	while(dinic_bfs(s,t)){//如果求解层次网络成功
		if(tp=dinic_dfs(s,t,inf)){//则求解增广路的最大流量
			ans+=tp;//最大流加上当前求解的增广路的流量
		}
	}
	return ans;//返回最大流
}

例一
ZOJ 2706 How Many Shortest Path
地址 : http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=1760
解题思路:通过所给的邻接矩阵,使用弗洛伊德算法计算最短路径,而后建立edge图,将每个流量置为1,最后使用Dinic算法计算最汇点的流量,即是最后有几条互不相交的最短路经。
代码:

#include<iostream>
#include<algorithm>
#include<string>
#include<sstream>
#include<vector>
#include<map>
#include<queue>
#include<bitset>
#include<list>
#include<stack>
#include<iterator>
#include<fstream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
using namespace std;
const int nMax=200;
const int mMax=100005;
const int inf=1<<29;
class Edge{
	public:
	int c,f;
};
Edge edge[205][205];
int level[205],N;
bool dinic_bfs(int s,int t){
	memset(level,0,sizeof(level));
	queue<int>q;
	q.push(s);
	level[s]=1;
	while(!q.empty()){
		int tmp=q.front();
		q.pop();
		for(int i=1;i<=N;i++){
			if(!level[i]&&edge[tmp][i].c>edge[tmp][i].f){
				level[i]=level[tmp]+1;
				q.push(i);
			}
		}
	}
	return level[t]!=0;
}

int dinic_dfs(int u,int t,int cp){
	int tmp=cp;
	if(u==t){
		return cp;
	}
	for(int i=1;i<=N;i++){
		if(level[u]+1==level[i]){
			if(edge[u][i].c>edge[u][i].f){
				int x=dinic_dfs(i,t,min(tmp,edge[u][i].c-edge[u][i].f));
				edge[u][i].f+=x;
				edge[i][u].f-=x;
				tmp-=x;
			}
		}
	}
	return cp-tmp;
}

int dinic(int s,int t){
	int ans=0,tp;
	while(dinic_bfs(s,t)){
		if(tp=dinic_dfs(s,t,inf)){
			ans+=tp;
		}
	}
	return ans;
}
int mp[nMax][nMax];
int maz[nMax][nMax];
void floyd(int N){//使用floyd算法求解最短路径
	for(int k=1;k<=N;k++){
		for(int i=1;i<=N;i++){
			for(int j=1;j<=N;j++){
				maz[i][j]=min(maz[i][j],maz[i][k]+maz[k][j]);
			}
		}
	}
}
void buildmap(int s,int t,int N){//建立edge图
	memset(edge,0,sizeof(edge));
	for(int i=1;i<=N;i++){
		if(maz[s][i]==inf){
			continue;
		}
		for(int j=1;j<=N;j++){
			if(i==j||maz[j][t]==inf||maz[i][j]==inf){
				continue;
			}
			if(maz[s][t]==maz[s][i]+mp[i][j]+maz[j][t]){
				edge[i][j].c=1;
			}
		}
	}
}
int main(){
	int i,j,s,t;
	while(~scanf("%d",&N)){
		for(i=1;i<=N;i++){
			for(j=1;j<=N;j++){
				scanf("%d",&maz[i][j]);
				if(maz[i][j]==-1){
					maz[i][j]=inf;
				}
				if(i==j){
					maz[i][j]=0;					
				}
				mp[i][j]=maz[i][j];
			}
		}
		scanf("%d%d",&s,&t);
		s++;t++;
		if(s==t){
			cout<<"inf"<<endl;
			continue;
		}
		floyd(N);
		buildmap(s,t,N);
		cout<<dinic(s,t)<<endl;
	}
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流浪天涯无迹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值