编写算法由依次输入的顶点数目_网络流算法学习笔记1(简洁易懂,强推)—最大流问题Ford fulkerson和Dinic算法及代码...

网络流
最大流
二分图匹配
对增广路的深入思考
最小费用流
转运问题
运输问题
任务分配问题
线性规划

很多问题都可以抽象为包含顶点和边有容量限制的网络。本章从实际需求出发,介绍解决这些特定问题的算法。

任务分配——有一堆任务待分配。对不同任务,不同员工所需费用不同。现在要求找到一种分配方式使得总费用最小。

二分图匹配——一下求职者要面试一系列工作岗位,现在要求找到一种合理的分配方式,使得可能多的人找到他们能胜任的工作。

最大流——给定一个网络,网络中每条边显示两地间潜在货运量,现在要求计算网络能支持的最大流量。

运输问题——确定从工厂向零售商店运输商品性价比最高的方法。

转运问题——确定从工厂向零售商店运输商品性价比最高的方法。但在这类问题中,我们可以使用一些中转站作为临时仓库。

下图展示了将以上问题转化为网络流问题的过程,他们都是从一或多个源点流向一或多个汇点。

3c5d5dfc-d517-eb11-8da9-e4434bdf6706.png

在本章,我们将阐述Ford-fulkerson算法,它用于解最大流问题,当然也可直接用于二分图匹配问题(如上图)。此外,一旦理解了该算法,最小费用流问题如转运问题、运输问题、任务分配问题也能相应解出。

1网络流

流网络可抽象为有向图G=(V,E)。V:顶点集,E:边集。
图是连通的。有一个特殊的源点s,负责生产商品,商品通过图的边运输到汇点t进行消费。

每条边(u,v)有一个流量f(u,v),表示从u运输到v的商品单位数目,一个容量c(u,v),表示能从u运输到v的最多的商品单元数目。

3d5d5dfc-d517-eb11-8da9-e4434bdf6706.png

可行流需满足三大条件:容量限制 流量守恒 反对称性

3e5d5dfc-d517-eb11-8da9-e4434bdf6706.png

接下来的算法中我们提到的网络路径指的是没有环的路径,有不同顶点<v1,v2,...,vn>连接的n-1条边构成。

2最大流

在一个流网络中,如果给定了边集E中所有有向边e=(u,v)的容量限制c(u,v),我们可以计算出顶点s和t之间的最大流。也就是说,在每条边都有容量限制的情况下,从源顶点s输出的,通过这个网络达到汇点的最大流量是可以计算出来的,从一个可行的最小流(例如每条边的流量为0的流)开始,Ford- Fulkerson算法会持续寻找从s到t的增广路径,继而增加更多的流。如果再也无法找到增广路径,那么算法终止。最大流最小割定理(Ford- Fulkerson,1962)保证了,在没有非负流量和非负客量限制的前提下,Ford- Fulkerson算法总是能够终止并找出网络中的最大流。

流网络可以定义为指定源顶点和终点的图G=(V,E),其中E中的每条有向边都有一个整数容量c(u,v)和实际流量f(u,v)。路径前向边和后向边组成,前向边是指由连续顶点

组成的边,而后向边是指边
∈E,其中路径会按照边的反方向进行遍历。

2.1输入/输出

对于每条边(u,v),Ford- Fulkerson算法会计算出整数流量f(u,v)。该算法结束时还会顺带输出网络的最小割,即由边集组成的一个瓶颈,来防止更多的单元在网络中从s流向t。

2.1解决方案

处理最大流问题最著名的就是Ford- Fulkerson算法,它包含了几种运行时间不同的具体实现。Ford- Fulkerson算法主要包含3个子算法思想:残差网络思想、增广路径思想、最大流最小割思想。
整体上,Ford- Fulkerson算法思想是一个迭代算法。初始时假定所有路径流量为0.每次迭代时通过增广路经扩大流量,利用最大流最小割定理判定算法运行结束。为了更好地理解算法,我们先分别介绍Ford- Fulkerson算法的3个组成部分。

1)残差网络
它是流网络的一部分。我们定义在不违反流网络G中容量限制的条件下,从u到v可以压入的额外流量就是流网络G中u、v的残留流量,容纳这些残留流量的边组成的子网络就是残留网络。残留网络的流量满足

注意:

可以大于流网络的容量
,这种情况只在网络中存在反向网络时产生。如
.

2)增广路经
残留网络

中从源点s到汇点t的一条简单路径。

增广路经的流量满足

405d5dfc-d517-eb11-8da9-e4434bdf6706.png

代表增广路经的残留容量,即沿增广路经p每条表传输的最大网络流量。

415d5dfc-d517-eb11-8da9-e4434bdf6706.png

3)流网络的割

425d5dfc-d517-eb11-8da9-e4434bdf6706.png

435d5dfc-d517-eb11-8da9-e4434bdf6706.png

最小割:一个流网络中净流量最小的割。

445d5dfc-d517-eb11-8da9-e4434bdf6706.png

割的容量

455d5dfc-d517-eb11-8da9-e4434bdf6706.png

最大流最小割定理:1)2)3)等价1)流f是流网络的最大流 2)残留网络G_{f}不含增广路经3 )对G的某割,存在关系|f|=c(S,T)

下面开始Ford-folkerson算法的正式表演:

迭代算法。开始时任意找出从s到t的一条增广路p,往p上每条边的流量f加上残留容量,迭代此过程,更新每一对顶点间网络流直到不存在增广路。

伪代码:

Ford-F(G,s,t)
for each edge (u,v) in E[G]
  do f(u,v)=0
while  exist a path p from s to t in  
  do  :min(  |(u,v) in p}
  for each edge (u,v) in p:
    do f(u,v)+=  ,f(v,u)=-f(u,v)

正式c++代码:(广度优先搜素增广路经)

// C++ program for implementation of Ford Fulkerson algorithm 
#include <iostream> 
#include <limits.h> 
#include <string.h> 
#include <queue> 
using namespace std; 

// Number of vertices in given graph 
#define V 6 

/* Returns true if there is a path from source 's' to sink 't' in 
residual graph. Also fills parent[] to store the path */
bool bfs(int rGraph[V][V], int s, int t, int parent[]) 
{ 
	// Create a visited array and mark all vertices as not visited 
	bool visited[V]; 
	memset(visited, 0, sizeof(visited)); 

	// Create a queue, enqueue source vertex and mark source vertex 
	// as visited 
	queue <int> q; 
	q.push(s); 
	visited[s] = true; 
	parent[s] = -1; 

	// Standard BFS Loop 
	while (!q.empty()) 
	{ 
		int u = q.front(); 
		q.pop(); 

		for (int v=0; v<V; v++) 
		{ 
			if (visited[v]==false && rGraph[u][v] > 0) 
			{ 
				q.push(v); 
				parent[v] = u; 
				visited[v] = true; 
			} 
		} 
	} 

	// If we reached sink in BFS starting from source, then return 
	// true, else false 
	return (visited[t] == true); 
} 

// Returns the maximum flow from s to t in the given graph 
int fordFulkerson(int graph[V][V], int s, int t) 
{ 
	int u, v; 

	// Create a residual graph and fill the residual graph with 
	// given capacities in the original graph as residual capacities 
	// in residual graph 
	int rGraph[V][V]; // Residual graph where rGraph[i][j] indicates 
					// residual capacity of edge from i to j (if there 
					// is an edge. If rGraph[i][j] is 0, then there is not) 
	for (u = 0; u < V; u++) 
		for (v = 0; v < V; v++) 
			rGraph[u][v] = graph[u][v]; 

	int parent[V]; // This array is filled by BFS and to store path 

	int max_flow = 0; // There is no flow initially 

	// Augment the flow while tere is path from source to sink 
	while (bfs(rGraph, s, t, parent)) 
	{ 
		// Find minimum residual capacity of the edges along the 
		// path filled by BFS. Or we can say find the maximum flow 
		// through the path found. 
		int path_flow = INT_MAX; 
		for (v=t; v!=s; v=parent[v]) 
		{ 
			u = parent[v]; 
			path_flow = min(path_flow, rGraph[u][v]); 
		} 

		// update residual capacities of the edges and reverse edges 
		// along the path 
		for (v=t; v != s; v=parent[v]) 
		{ 
			u = parent[v]; 
			rGraph[u][v] -= path_flow; 
			rGraph[v][u] += path_flow; 
		} 

		// Add path flow to overall flow 
		max_flow += path_flow; 
	} 

	// Return the overall flow 
	return max_flow; 
} 

// Driver program to test above functions 
int main() 
{ 
	// Let us create a graph shown in the above example 
	int graph[V][V] = { {0, 16, 13, 0, 0, 0}, 
						{0, 0, 10, 12, 0, 0}, 
						{0, 4, 0, 0, 14, 0}, 
						{0, 0, 9, 0, 0, 20}, 
						{0, 0, 0, 7, 0, 4}, 
						{0, 0, 0, 0, 0, 0} 
					}; 

	cout << "The maximum possible flow is " << fordFulkerson(graph, 0, 5); 

	return 0; 
} 

Output:

The maximum possible flow is 23

Ford-Fulkerson算法的上述实现称为Edmonds-Karp算法。Edmonds-Karp的想法是在Ford Fulkerson实现中使用BFS,因为BFS总是选择一条边数最少的路径(由于DFS)。设计优良的广度优先搜索可以在O(V+E)时间内找到增广路径(注意:上述代码BFS花费的则是O(E^2)时间),实际上就是O(E),因为连通的流网络中V比E少得多。cormen2009证明了Edmonds-Karp算法的性能是O(VE^2)。

Dinic最大流算法

Edmond Karp实现的时间复杂度为O(VE^2),而Dinic算法更快,时间复杂度为O(EV^2)。
与Edmond Karp的算法一样,Dinic的算法使用以下概念:

  1. 如果残差图中没有s-t路径,则流量最大。
  2. BFS循环使用。虽然在两种算法中使用BFS的方式有所不同。

在Edmond-Karp算法中,我们使用BFS查找增广路经并通过该路径发送流。在Dinic算法中,我们使用BFS来检查是否有可能有更多的流量,并构造level graph。在level graph中,我们为所有节点分配level ,一个节点的level 是该节点到源的最短距离(以边的数量表示)。一旦构建了level 图,我们就可以使用该level 图发送多个流。这就是它比Edmond Karp更好的原因:在Edmond Karp中,我们仅发送通过BFS所找路径发送的流。

Dinic算法概述:

1)将残差图G初始化为给定图。
1)进行G的BFS构造一个level 图(或为顶点分配level ),并检查是否更多的流量是可能的。
a)如果不可能有更多的流量,则返回。
b)使用level 图在G中发送多个流 直到达到阻塞流量。在这里使用level 图表示在每个flow中从s到t路径节点的level 应为0、1、2 ...(按顺序)。

如果无法使用level 图发送更多的流,则该流为“阻塞流”,即,不再存在任何s-t路径,从而路径顶点的level依次为0、1、2…。阻塞流可以视为与此处讨论的贪婪算法中的最大流路相同。
初始残差图(与给定图相同)

465d5dfc-d517-eb11-8da9-e4434bdf6706.png

总流量= 0

第一次迭代:我们使用BFS为所有节点分配level。我们还会检查是否有更多流量(或残差图中有s-t路径)。

485d5dfc-d517-eb11-8da9-e4434bdf6706.png

现在,我们使用level来发现阻塞流(这意味着每个流路径的level都应为0、1、2、3)。我们一起发送三个流。与我们一次发送一个流的Edmond Karp相比,这是优化的地方。
路径s – 1 – 3 – t上有4个流量单位。
路径s – 1 – 4 – t上有6个流量单位。
路径s – 2 – 4 – t上有4个流量单位。
总流量=总流量+ 4 + 6 + 4 = 14
一轮迭代后,残差图变为以下。

4a5d5dfc-d517-eb11-8da9-e4434bdf6706.png

第二次迭代:我们使用上述修改后的残差图的BFS为所有节点分配新level。我们还会检查是否有更多流量(或残差图中有st路径)。

4c5d5dfc-d517-eb11-8da9-e4434bdf6706.png

现在,我们发现使用level来阻塞流(意味着每个流路径的level都应为0、1、2、3、4)。这次我们只能发送一个流。
路径s上5个流量单位s– 2 – 4 – 3 – t
总流量=总流量+ 5 = 19
新的残差图是

4d5d5dfc-d517-eb11-8da9-e4434bdf6706.png

第三次迭代:我们运行BFS并创建一个level图。我们还会检查是否有更多流量,并仅在可能的情况下进行。这次残差图中没有st路径,因此我们终止了算法。

以下是Dinic算法的c ++实现:

// C++ implementation of Dinic's Algorithm 
#include<bits/stdc++.h> 
using namespace std; 

// A structure to represent a edge between 
// two vertex 
struct Edge 
{ 
	int v ; // Vertex v (or "to" vertex) 
			// of a directed edge u-v. "From" 
			// vertex u can be obtained using 
			// index in adjacent array. 

	int flow ; // flow of data in edge 

	int C; // capacity 

	int rev ; // To store index of reverse 
			// edge in adjacency list so that 
			// we can quickly find it. 
}; 

// Residual Graph 
class Graph 
{ 
	int V; // number of vertex 
	int *level ; // stores level of a node 
	vector< Edge > *adj; 
public : 
	Graph(int V) 
	{ 
		adj = new vector<Edge>[V]; 
		this->V = V; 
		level = new int[V]; 
	} 

	// add edge to the graph 
	void addEdge(int u, int v, int C) 
	{ 
		// Forward edge : 0 flow and C capacity 
		Edge a{v, 0, C, adj[v].size()}; 

		// Back edge : 0 flow and 0 capacity 
		Edge b{u, 0, 0, adj[u].size()}; 

		adj[u].push_back(a); 
		adj[v].push_back(b); // reverse edge 
	} 

	bool BFS(int s, int t); 
	int sendFlow(int s, int flow, int t, int ptr[]); 
	int DinicMaxflow(int s, int t); 
}; 

// Finds if more flow can be sent from s to t. 
// Also assigns levels to nodes. 
bool Graph::BFS(int s, int t) 
{ 
	for (int i = 0 ; i < V ; i++) 
		level[i] = -1; 

	level[s] = 0; // Level of source vertex 

	// Create a queue, enqueue source vertex 
	// and mark source vertex as visited here 
	// level[] array works as visited array also. 
	list< int > q; 
	q.push_back(s); 

	vector<Edge>::iterator i ; 
	while (!q.empty()) 
	{ 
		int u = q.front(); 
		q.pop_front(); 
		for (i = adj[u].begin(); i != adj[u].end(); i++) 
		{ 
			Edge &e = *i; 
			if (level[e.v] < 0 && e.flow < e.C) 
			{ 
				// Level of current vertex is, 
				// level of parent + 1 
				level[e.v] = level[u] + 1; 

				q.push_back(e.v); 
			} 
		} 
	} 

	// IF we can not reach to the sink we 
	// return false else true 
	return level[t] < 0 ? false : true ; 
} 

// A DFS based function to send flow after BFS has 
// figured out that there is a possible flow and 
// constructed levels. This function called multiple 
// times for a single call of BFS. 
// flow : Current flow send by parent function call 
// start[] : To keep track of next edge to be explored. 
//		 start[i] stores count of edges explored 
//		 from i. 
// u : Current vertex 
// t : Sink 
int Graph::sendFlow(int u, int flow, int t, int start[]) 
{ 
	// Sink reached 
	if (u == t) 
		return flow; 

	// Traverse all adjacent edges one -by - one. 
	for ( ; start[u] < adj[u].size(); start[u]++) 
	{ 
		// Pick next edge from adjacency list of u 
		Edge &e = adj[u][start[u]]; 
									
		if (level[e.v] == level[u]+1 && e.flow < e.C) 
		{ 
			// find minimum flow from u to t 
			int curr_flow = min(flow, e.C - e.flow); 

			int temp_flow = sendFlow(e.v, curr_flow, t, start); 

			// flow is greater than zero 
			if (temp_flow > 0) 
			{ 
				// add flow to current edge 
				e.flow += temp_flow; 

				// subtract flow from reverse edge 
				// of current edge 
				adj[e.v][e.rev].flow -= temp_flow; 
				return temp_flow; 
			} 
		} 
	} 

	return 0; 
} 

// Returns maximum flow in graph 
int Graph::DinicMaxflow(int s, int t) 
{ 
	// Corner case 
	if (s == t) 
		return -1; 

	int total = 0; // Initialize result 

	// Augment the flow while there is path 
	// from source to sink 
	while (BFS(s, t) == true) 
	{ 
		// store how many edges are visited 
		// from V { 0 to V } 
		int *start = new int[V+1]; 

		// while flow is not zero in graph from S to D 
		while (int flow = sendFlow(s, INT_MAX, t, start)) 

			// Add path flow to overall flow 
			total += flow; 
	} 

	// return maximum flow 
	return total; 
} 

// Driver program to test above functions 
int main() 
{ 
	Graph g(6); 
	g.addEdge(0, 1, 16 ); 
	g.addEdge(0, 2, 13 ); 
	g.addEdge(1, 2, 10 ); 
	g.addEdge(1, 3, 12 ); 
	g.addEdge(2, 1, 4 ); 
	g.addEdge(2, 4, 14); 
	g.addEdge(3, 2, 9 ); 
	g.addEdge(3, 5, 20 ); 
	g.addEdge(4, 3, 7 ); 
	g.addEdge(4, 5, 4); 

	// next exmp 
	/*g.addEdge(0, 1, 3 ); 
	g.addEdge(0, 2, 7 ) ; 
	g.addEdge(1, 3, 9); 
	g.addEdge(1, 4, 9 ); 
	g.addEdge(2, 1, 9 ); 
	g.addEdge(2, 4, 9); 
	g.addEdge(2, 5, 4); 
	g.addEdge(3, 5, 3); 
	g.addEdge(4, 5, 7 ); 
	g.addEdge(0, 4, 10); 

	// next exp 
	g.addEdge(0, 1, 10); 
	g.addEdge(0, 2, 10); 
	g.addEdge(1, 3, 4 ); 
	g.addEdge(1, 4, 8 ); 
	g.addEdge(1, 2, 2 ); 
	g.addEdge(2, 4, 9 ); 
	g.addEdge(3, 5, 10 ); 
	g.addEdge(4, 3, 6 ); 
	g.addEdge(4, 5, 10 ); */

	cout << "Maximum flow " << g.DinicMaxflow(0, 5); 
	return 0; 
} 

Output:

Maximum flow 23

时间复杂度:O(EV2)。进行BFS构造level图需要O(E)时间。发送更多流量直到达到阻塞流量需要O(VE)时间。外循环最多运行O(V)时间。在每次迭代中,我们构造新的level图并查找阻塞流。可以证明,level的数量在每次迭代中至少增加了一个(请参见https://www.youtube.com/watch?v=uM06jHdIC70作为证明)。因此,外循环最多运行O(V)次。因此,总体时间复杂度为O(EV^2)。

最大流算法变体(添加了顶点容量或无向边该咋办呐)

4f5d5dfc-d517-eb11-8da9-e4434bdf6706.png

505d5dfc-d517-eb11-8da9-e4434bdf6706.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Ford-Fulkerson算法是求解最大问题的一种经典算法。以下是一个基于增广路思想的Ford-Fulkerson算法代码: ``` // 基于邻接矩阵的Ford-Fulkerson算法实现 #include <iostream> #include <queue> #include <cstring> using namespace std; const int MAXN = 100; // 最大顶点数 const int INF = 0x3f3f3f3f; // 表示无穷大 int n, m; // n表示顶点数,m表示边数 int s, t; // s表示源点,t表示汇点 int cap[MAXN][MAXN]; // 表示容量 int flow[MAXN][MAXN]; // 表示量 int pre[MAXN]; // 表示前驱节点 int bfs() { memset(pre, -1, sizeof(pre)); // 初始化前驱节点数组 queue<int> q; q.push(s); pre[s] = -2; while (!q.empty()) { int u = q.front(); q.pop(); for (int v = 0; v < n; ++v) { if (pre[v] == -1 && cap[u][v] > flow[u][v]) { pre[v] = u; if (v == t) return 1; q.push(v); } } } return 0; } int maxFlow() { int ans = 0; while (bfs()) { int minflow = INF; for (int u = t; u != s; u = pre[u]) { int v = pre[u]; minflow = min(minflow, cap[v][u] - flow[v][u]); } for (int u = t; u != s; u = pre[u]) { int v = pre[u]; flow[v][u] += minflow; flow[u][v] -= minflow; } ans += minflow; } return ans; } int main() { cin >> n >> m >> s >> t; memset(cap, 0, sizeof(cap)); memset(flow, 0, sizeof(flow)); for (int i = 0; i < m; ++i) { int u, v, c; cin >> u >> v >> c; cap[u][v] += c; // 注意有可能存在重边 } cout << maxFlow() << endl; return 0; } ``` 算法思路: 1. 初始化量为0; 2. 在剩余容量大于0的情况下,寻找增广路: - 从源点s开始,使用BFS寻找一条增广路; - 如果找到增广路,计算增广路上的最小剩余容量minflow,更新量; 3. 最大就是所有增广路上的最小剩余容量之和。 其中,增广路的定义是指从源点到汇点路径上,剩余容量均大于0的路径。在Ford-Fulkerson算法中,每次都需要寻找一条增广路来更新量,直到无法再找到增广路为止。这个过程中,每次找到的增广路都可以使得量增加,因此最终的量是不断增加的。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值