最大流
最大流即为最大可行流,最大流的流量是所有可行流中最大的。
实现最大流算法,通常可以使用Ford-Fulkerson算法或它的改进版本Edmonds-Karp算法。这些算法基于图论中的网络流理论,用于在带权有向图中找到从一个顶点到另一个顶点的最大流量。
以下是一个使用Edmonds-Karp算法实现最大流的简单示例。在这个例子中,我们将创建一个简单的网络图,并计算从源点到汇点的最大流。
#include <stdio.h>
#include <limits.h>
#define V 6 // 顶点的数量
int graph[V][V]; // 图的邻接矩阵表示
int parent[V]; // 用于记录增广路径的父节点
int max_flow(int s, int t, int rGraph[V][V]) {
int u, v;
int flow = 0;
while (1) {
// 初始化parent数组为-1,表示当前没有增广路径
for (v = 0; v < V; v++)
parent[v] = -1;
// 使用BFS从源点s开始寻找增广路径
int queue[V];
int front = 0, rear = 0;
queue[rear++] = s;
parent[s] = -2; // 源点的parent设为-2
while (front < rear) {
u = queue[front++];
for (v = 0; v < V; v++) {
if (parent[v] == -1 && rGraph[u][v] > 0) {
parent[v] = u;
queue[rear++] = v;
}
}
}
// 如果没有找到增广路径,则退出循环
if (parent[t] == -1)
break;
// 找到增广路径后,计算路径上的最小残留容量
int path_flow = INT_MAX;
for (v = t; v != s; v = parent[v]) {
u = parent[v];
path_flow = (path_flow < rGraph[u][v]) ? path_flow : rGraph[u][v];
}
// 更新残留网络中的容量
for (v = t; v != s; v = parent[v]) {
u = parent[v];
rGraph[u][v] -= path_flow;
rGraph[v][u] += path_flow;
}
// 累加路径流量
flow += path_flow;
}
return flow;
}
int main() {
// 初始化图的容量
int capacity[][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}
};
// 复制容量矩阵到残留网络矩阵
for (int i = 0; i < V; i++) {
for (int j = 0; j < V; j++) {
graph[i][j] = capacity[i][j];
}
}
int s = 0; // 源点
int t = 5; // 汇点
int max_flow_value = max_flow(s, t, graph);
printf("The maximum possible flow is %d\n", max_flow_value);
return 0;
}
这个示例中,我们首先定义了一个图的邻接矩阵graph
来表示网络中的容量。然后,我们实现了max_flow
函数来计算从源点s
到汇点t
的最大流。这个函数使用BFS来寻找增广路径,并更新残留网络中的容量,直到没有更多的增广路径为止。最后,我们在main
函数中初始化图的容量,并调用max_flow
函数来计算最大流。
1. 流网络
流网络是一种特殊的有向图。
一个网络可以表示成一个点集和边集的集合,即:G=(V,E)。
V表示点,流网络有两个特殊点,分别是源点和汇点。
E表示边,流网络中每条边都有一个权重c,被称之为容量(Capacity)。
(1)源点 & 汇点
可以把流网络类比成一个由一条条河流组成的网络。
源点(Source)有无穷的的流量可以向外流出,汇点(Sink)有无穷的容量容纳流量。
(2)净流
通过流网络的一条边的流量(净流)记为f(u,v)。
在C语言中实现流网络的案例,我们可以使用Ford-Fulkerson算法或其变种,如Edmonds-Karp算法,来找到给定网络中的最大流。以下是一个使用Ford-Fulkerson算法实现最大流的简单C语言案例
#include <stdio.h>
#include <limits.h>
#define V 6 // 顶点数
int R[V][V]; // 残量网络
int u, v;
// BFS函数用于找到从源点到汇点的增广路径
int BFS(int rGraph[V][V], int s, int t, int parent[]) {
// 初始化parent数组和visited数组
int parent_temp[V];
int visited[V];
for (int i = 0; i < V; i++) {
parent_temp[i] = -1;
visited[i] = 0;
}
// 创建队列,并将源点入队
int queue[V];
int front = 0, rear = 0;
queue[rear++] = s;
visited[s] = 1;
// BFS遍历
while (front < rear) {
u = queue[front++];
for (v = 0; v < V; v++) {
if (visited[v] == 0 && rGraph[u][v] > 0) {
queue[rear++] = v;
parent_temp[v] = u;
visited[v] = 1;
if (v == t) {
return 1; // 找到增广路径
}
}
}
}
// 没有找到增广路径
return 0;
}
// Ford-Fulkerson算法函数
int FordFulkerson(int graph[V][V], int s, int t) {
int u, v;
// 初始化残量网络为原始容量网络
for (u = 0; u < V; u++) {
for (v = 0; v < V; v++) {
R[u][v] = graph[u][v];
}
}
int parent[V];
int max_flow = 0; // 最大流的初始值
// 不断寻找增广路径,直到找不到为止
while (BFS(R, s, t, parent)) {
// 找到增广路径后,计算路径上的最小残量
int path_flow = INT_MAX;
for (v = t; v != s; v = parent[v]) {
u = parent[v];
path_flow = (path_flow < R[u][v]) ? path_flow : R[u][v];
}
// 更新残量网络
for (v = t; v != s; v = parent[v]) {
u = parent[v];
R[u][v] -= path_flow;
R[v][u] += path_flow;
}
// 累加路径流量
max_flow += path_flow;
}
return max_flow;
}
int main() {
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}
};
int s = 0; // 源点
int t = 5; // 汇点
int max_flow = FordFulkerson(graph, s, t);
printf("The maximum possible flow is %d\n", max_flow);
return 0;
}
在这个例子中,我们首先定义了一个二维数组graph
来表示网络中的容量。然后,我们实现了Ford-Fulkerson算法的核心逻辑。这个算法不断通过BFS查找增广路径,更新残量网络,直到没有增广路径可找为止。最后,我们在`main。
可行流
可行流常用 f 表示,在流网络中满足以下条件的网络流称为可行流。
(1)容量限制(Capacity Constraints):
(2)流守恒(Flow Conservation):除了源点和汇点,所有点流出流量之和=流入流量之和
反之,如果只满足容量限制不满足流守恒则称之为伪流。
在C语言中实现可行流的案例,我们首先需要明确什么是可行流。在流网络中,一个可行流是指满足所有顶点流量守恒(即进入每个顶点的流量等于离开该顶点的流量,除了源点和汇点)并且不超过每条边的容量的流。如果网络中的流既满足容量约束又满足守恒条件,那么它就是可行的。
下面是一个简单的C语言程序,它演示了如何检查一个给定的流是否是可行流。在这个例子中,我们假设已经有一个流分配在网络的边上,并且我们想要验证这个流是否满足所有顶点的流量守恒以及每条边的容量约束。
#include <stdio.h>
#include <stdbool.h>
#define V 6 // 顶点数
// 网络中的容量
int capacity[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}
};
// 网络中的流
int flow[V][V] = {
{0, 10, 0, 0, 0, 0},
{0, 0, 7, 12, 0, 0},
{0, 4, 0, 0, 10, 0},
{0, 0, 0, 0, 0, 0},
{0, 0, 3, 0, 0, 4},
{0, 0, 0, 0, 0, 0}
};
// 检查顶点流量是否守恒
bool checkFlowConservation(int flow[V][V], int s, int t) {
int inFlow[V] = {0};
int outFlow[V] = {0};
for (int u = 0; u < V; u++) {
for (int v = 0; v < V; v++) {
if (u != v) {
inFlow[v] += flow[u][v];
outFlow[u] += flow[u][v];
}
}
}
// 源点流出的流量可以超过流入的流量,汇点流入的流量可以超过流出的流量
if (inFlow[s] != 0 || outFlow[t] != 0) {
return false;
}
for (int i = 0; i < V; i++) {
if (i != s && i != t && inFlow[i] != outFlow[i]) {
return false;
}
}
return true;
}
// 检查流是否满足容量约束
bool checkCapacityConstraints(int flow[V][V], int capacity[V][V]) {
for (int u = 0; u < V; u++) {
for (int v = 0; v < V; v++) {
if (flow[u][v] > capacity[u][v]) {
return false;
}
}
}
return true;
}
int main() {
// 检查流是否可行
if (checkFlowConservation(flow, 0, 5) && checkCapacityConstraints(flow, capacity)) {
printf("The given flow is feasible.\n");
} else {
printf("The given flow is not feasible.\n");
}
return 0;
}
在这个程序中,我们定义了两个二维数组capacity
和flow
,分别表示网络中的容量和流。checkFlowConservation
函数用于检查流量守恒,即每个非源点非汇点的顶点的流入流量是否等于流出流量。checkCapacityConstraints
函数用于检查每条边上的流是否没有超过其容量。最后,在main
函数中,我们调用这两个函数来验证给定的流是否是可行的。
请注意,这个程序假设流和容量都是非负的,并且源点和汇点已经被预先确定。在实际应用中,您可能需要根据具体情况调整这些假设和检查逻辑。
可行流的流量
每秒从源点流出的净流量或者每秒从汇点流入的净流量就是该可行流的流量:
式子也可以用汇点来表示。式子后半部分表示减去流回源点的流量。
在C语言中,可行流(Feasible Flow)通常指的是满足所有顶点的流量守恒条件(即流入每个顶点的流量等于流出该顶点的流量,除了源点和汇点)以及每条边的容量限制的流。要演示一个可行流的流量案例,我们首先需要定义网络结构,包括顶点、边的容量以及每条边上的流量。
下面是一个简单的C语言程序,它定义了一个网络结构和一个可行流,并验证这个流是否满足可行流的条件。
#include <stdio.h>
#include <stdbool.h>
#define V 6 // 顶点数
// 网络中的容量
int capacity[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}
};
// 网络中的流
int flow[V][V] = {
{0, 10, 0, 0, 0, 0},
{0, 0, 7, 5, 0, 0},
{0, 4, 0, 0, 3, 0},
{0, 0, 0, 0, 0, 5},
{0, 0, 10, 0, 0, 4},
{0, 0, 0, 0, 0, 0}
};
// 检查顶点流量是否守恒
bool checkFlowConservation(int flow[V][V]) {
int inFlow[V] = {0};
int outFlow[V] = {0};
for (int u = 0; u < V; u++) {
for (int v = 0; v < V; v++) {
if (u != v) {
inFlow[v] += flow[u][v];
outFlow[u] += flow[u][v];
}
}
}
// 源点流出的流量可以大于流入的流量,汇点流入的流量可以大于流出的流量
int s = 0; // 源点
int t = 5; // 汇点
if (inFlow[s] > 0 || outFlow[t] > 0) {
return false;
}
for (int i = 0; i < V; i++) {
if (i != s && i != t && inFlow[i] != outFlow[i]) {
return false;
}
}
return true;
}
// 检查流是否满足容量约束
bool checkCapacityConstraints(int flow[V][V], int capacity[V][V]) {
for (int u = 0; u < V; u++) {
for (int v = 0; v < V; v++) {
if (flow[u][v] > capacity[u][v] || flow[u][v] < 0) {
return false;
}
}
}
return true;
}
int main() {
// 检查流是否可行
if (checkFlowConservation(flow) && checkCapacityConstraints(flow, capacity)) {
printf("The given flow is feasible.\n");
} else {
printf("The given flow is not feasible.\n");
}
return 0;
}
在这个程序中,capacity
数组表示每条边的容量,而flow
数组表示网络中的实际流量。checkFlowConservation
函数用于检查流量守恒,而checkCapacityConstraints
函数用于检查流量是否满足每条边的容量限制。
如果flow
数组中的流量满足所有顶点的流量守恒并且每条边上的流量都没有超过其容量,那么程序将输出“The given flow is feasible.”。否则,它将输出“The given flow is not feasible.”。
请注意,这个案例假设了网络中的源点和汇点分别是顶点0和顶点5。在实际应用中,您可能需要根据具体的网络结构和需求来调整这些值。此外,这个案例没有考虑如何计算或生成可行流,只是验证了一个。
一、流网络
G=(V,E)是一个有向图,其中每条边(u,v)有一个非负的容量值c(u,v),而且如果E中包含一条边(u,v),那么图中就不存在它的反向边。在流网络中有两个特殊的结点,源结点s和汇点t。
下面给出流网络的形式化定义。令G=(V,E)为一个流网络,其容量函数为c,设s我为网络的源点,t为汇点。G中的流是一个实值函数f,满足以下两条性质:
1. 容量限制(capacity contraint):对于所有的结点u,v,要求
2. 流量守恒(flow conservation):对于所有的非源点和汇点的结点u,要求:
下图是一个流网络的示例图,帮助大家理解,其中的“/”只是分隔符而不是运算符,“/”前代表的是流的值,后面的数值则是该条边的容量(capacity):
流网络常见的一种应用场景是运输问题,需要将货物从s运输到t,途经几个中转站,每次运输到每个中转站的货物的数量是有限制的。在实际应用中我们可能会在某条边上双向运输,这样便违反了我们之前对流网络的定义,但是我们可以将包含反平行边的图来改造成流网络,具体的方法是引入一个是虚构的中转结点,方法如下图。
考虑另外一种特殊情形,从多个工厂发出货物最终运输到别的多个工厂,这时候我们具有了多个源点和多个汇点,这也很好解决,解决的方法就是人为添加超级源点(supersource)和超级汇点(supersink),具体方法见下图。
C语言中实现流网络(Flow Network)的案例通常涉及定义网络结构、计算流量、检查流是否可行以及可能执行其他与网络流相关的操作,如最大流计算等。以下是一个简单的C语言流网络案例,它定义了一个流网络并演示了如何检查给定的流是否可行。
首先,我们需要定义网络的结构,包括顶点数、边的容量以及当前的流量。然后,我们将编写函数来检查流是否满足流量守恒以及是否没有超过边的容量。
#include <stdio.h>
#include <stdbool.h>
#define V 6 // 顶点数
// 网络中的容量
int capacity[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}
};
// 网络中的流
int flow[V][V] = {
{0, 10, 0, 0, 0, 0},
{0, 0, 7, 4, 0, 0},
{0, 4, 0, 0, 10, 0},
{0, 0, 0, 0, 0, 4},
{0, 0, 6, 0, 0, 4},
{0, 0, 0, 0, 0, 0}
};
// 检查顶点流量是否守恒
bool checkFlowConservation(int flow[V][V], int s, int t) {
int inFlow[V] = {0};
int outFlow[V] = {0};
for (int u = 0; u < V; u++) {
for (int v = 0; v < V; v++) {
if (u != v) {
inFlow[v] += flow[u][v];
outFlow[u] += flow[u][v];
}
}
}
// 源点流出的流量大于流入的流量,汇点流入的流量大于流出的流量
if (inFlow[s] > 0 || outFlow[t] > 0) {
return false;
}
for (int i = 0; i < V; i++) {
if (i != s && i != t && inFlow[i] != outFlow[i]) {
return false;
}
}
return true;
}
// 检查流是否满足容量约束
bool checkCapacityConstraints(int flow[V][V], int capacity[V][V]) {
for (int u = 0; u < V; u++) {
for (int v = 0; v < V; v++) {
if (flow[u][v] > capacity[u][v] || flow[u][v] < 0) {
return false;
}
}
}
return true;
}
int main() {
int s = 0; // 源点
int t = 5; // 汇点
// 检查流是否可行
if (checkFlowConservation(flow, s, t) && checkCapacityConstraints(flow, capacity)) {
printf("The given flow is feasible.\n");
} else {
printf("The given flow is not feasible.\n");
}
return 0;
}
在这个案例中,我们定义了一个包含6个顶点的流网络,并初始化了边的容量和当前流量。checkFlowConservation
函数用于检查流量守恒,而checkCapacityConstraints
函数用于检查流量是否满足容量限制。如果这两个条件都满足,那么流就是可行的。
请注意,这个案例并没有演示如何生成或计算流,而只是验证了一个给定的流是否是可行的。在实际应用中,您可能需要使用更复杂的算法(如Ford-Fulkerson算法或Edmonds-Karp算法)来计算最大流或其他与网络流相关的问题。
二、Ford-Fulkerson方法
将实际问题转化成流网络后,我们就可以来解决最大流问题了。理解这个方法需要先理解几个关于流网络的基础概念。
1. 残存网络(residual network)
假定有一个流网络G=(V,E),其源点为s,汇点为t,f为G中的一个流。对即诶点对u,v,定义残存容量(residual capacity)
,有:
残存网络可能包含图G中不存在的边,残存网络中的反向边允许算法将已经发送出来的流量发送回去。一个残存网络示例图如下:
图a是一个流网络,b是a对应的残存网络,注意每条边上的值,残存网络中针对每条正向边计算出该条边在存在流的情况下的剩余容量,并画出一条反向边,反向边的容量即是发出流的大小,方便将发出的流运输回发送地,并将权重为0的边省略。
残存网络是如何增大原始流网络中的流的一种指示。如果f是G的一个流,对应的有一个残存网络,残存网络中我们可以定义一个流 f’。此时我们可以定义一个函数 f↑f’,我们将其称作流f’对f的增量(augmentation)。
2. 增广路径(augmenting paths)
给定流网络G和流f,增广路径p是残存网络中一条从源结点s到汇点t的简单路径。根据残存网络的定义,对于一条增广路径上的边(u,v),我们可以增加其流量的幅度最大为
,即我们之前定义的残存容量(residual capacity)。我们将这里讨论的情形总结成一条引理:
引理设G为一个流网络,设f为图G中的一个流,设p为残存网络中的一条增广路径。定义一个函数
如下:
其中
是残存网络中的一个流,其值
。
推论设G为一个流网络,设f为G中的一个流,设p为残存网络中的一条增广路径。设
如上述引理所定义,假定将f增加
的量,则函数 f↑
是图G中的一个流,其值为
。