网络流 - 最大流算法之Dinic

看了几个博客,整理了一下      建议先了解下EK算法:点击查看

Dinic算法的核心在于:

1、对图进行BFS,绘制出层次图,即对每一个节点进行标记,记录其与源点的距离,称为dep深度。

2、对图进行DFS,过程基本同EK算法,但在搜索过程中,只遍历至当前节点深度+1的节点

3、反复进行1、2操作,直至第1步层次图中汇点dep值为空,即没有路可抵达汇点,表示最大流已求出,即完成任务。

在层次图上DFS求最大流自然很简单,就是不考虑反向边时能得到的最大流,多次增广后,重新计算层次图,发现s和t不连通,就退出。

 

它的时间复杂度不会很高,相对EK会快很多,最多只会跑n-1次DFS,因为每次DFS之后s到t的路径至少会少一条,也就是最大距离至少会增加1,而每一次跑DFS最多会用nm的时间,每一次最多遍历每一个节点,每一个节点遍历它所链接的所有边,所以最终时间复杂度为O(N^2*M),相对EK(N*M^2)来说会快很多。

实际上,理论数值所对应的数据几乎不会出现,通常并不会想理论值说的这么慢,只会更快。

而且,对于它,还有一个很重要的优化,对于DFS来说,要保存一个“当前所有弧的最小残量”,如果等于0,必然是不能增广的,找到一条路径直接返回它的值即可,不然会出现多路增广不会退出的情况,还需要多加一次再算增广量,必然会很慢。

还有,当前弧优化,对于一次DFS中,可能会重复访问一个节点,虽然可能性比较小,这时候,已经走过的边自然是不需要再走,那么用一个数组记录上一次这个点走了哪些连接它的边,再次访问时不再重复走,那么就会更快。

代码核心讲解:

void add(int u,int v,int w) // 建图
{
	e[len].to=v;
	e[len].cap=w;
	e[len].nex=head[u];
	head[u]=len++;
}
int bfs(int s,int t)
{
	memset(vis,0,sizeof(vis));
	queue<int> q;
	vis[s]=1;
	deep[s]=0;
	deep[t]=-1;
	q.push(s);
	while(!q.empty())
	{
		int now=q.front();q.pop();
		for(int i=head[now];i!=-1;i=e[i].nex)
		{
			int to=e[i].to;
			if(vis[to]||e[i].cap<=0) continue; // 残量为0 或者 已经分配深度了 就在进行操作
			deep[to]=deep[now]+1;
			vis[to]=1;
			q.push(to);
		}
	}
	if(deep[t]==-1) return 0; // 当不能到达汇点时 说明也就不存在增广路了
	return 1;
}
int dfs(int u,int t,int flow)
{
	if(u==t||flow==0) return flow; // 若已达到汇点 或者 当前流量为0  返回
	int res=0,f;
	for(int i=head[u];i!=-1;i=e[i].nex)
	{
		int to=e[i].to;
		if(deep[to]==deep[u]+1) // 首先他是增广路才继续
		{
            f=dfs(to,t,min(flow,e[i].cap));//
		    if(f<=0) continue; // 流量小于等于0 也就不进行了
			e[i].cap-=f;     // 正减 反加
			e[i^1].cap+=f;
			flow-=f;
			res+=f;
			if(!flow) break;
		}
	}
	return res;   // 返回此过程流量
}
int Dinic(int s,int t)
{
	int ans=0; // 记录最大流
	while(bfs(s,t))
	{
		ans+=dfs(s,t,INF);
	}
	return ans;
}

是不是想看个题 : poj1273 点击查看

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值