网络流算法: Edmonds-Karp算法

图论相关帖子

  • 基本概念
  • 图的表示: 邻接矩阵和邻接表
  • 图的遍历: 深度优先与广度优先
  • 拓扑排序
  • 图的最短路径:Dijkstra算法和Bellman-Ford算法
  • 最小生成树
  • 二分图
  • 多源最短路径
  • 强连通分量
  • 欧拉回路和汉密尔顿回路
  • 网络流算法: Edmonds-Karp算法
  • 网络流算法: Dinic算法

环境要求

本文所用样例在Windows 11以及Ubuntu 24.04上面编译通过.

  1. Windows: 使用[Visual Studio],
  2. Ubuntu: 使用 Clang 18.1.3. (Ubuntu 24.04 系统安装版本)
  3. GCC 无法编译直接本项目代码, 因为本文代码使用了 C++20 Module, 而 GCC 对此支持不完整.

Intro

网络流算法是一类用于解决在流网络中最大化流从源点到汇点问题的算法. 流网络是由节点和有向边构成的图, 每条边有一个容量限制, 表示可以通过该边的最大流量. 网络流问题的目标是找到一种流分配方式, 使得整个网络从源到汇的总流量最大.

在下图中, 节点 0 是源点, 节点 5 是汇点, 最大流问题就是求从源点到汇点的最大流量是多少.

网络流

下面是一种可行的解决方案. 路径权重中, 前者表示实际通过的流量, 后者表示其最大容量.

network capacity

Ford-Fulkerson 方法

Ford-Fulkerson 方法是一种用于解决最大流问题的经典算法, 它通过寻找增广路径(augmenting path)来逐步增加网络中从源点到汇点的流量, 直到无法再找到新的增广路径为止. 这种方法基于的是残量图和增广路径的概念.

残量图(Residual Graph)

残量图是从从网络流的状态衍生出来的. 它给每条边增加了反向边. 每个边的权重会重新调整: 假设其原来最大容量为 a a a, 当前流量为 b b b, 则参量图中这个边的最大容量为 a − b a-b ab, 反向边的流量为 b b b.

以下图为例, 这是一个原始图. 此时没有流量.

origin

下图中, 左侧是当前的流量状态, 而右侧则是残量图的状态.

参量图

我们可以看到:

  • 对于 (0 -> 1) 这条边, 总容量为3,目前容量为3. 所以在残量图中: (0 -> 1) 的最大容量为 3 − 3 = 0 3-3=0 33=0, 反向边(1 - > 0)的容量为 3 3 3.
  • 对于 (1 -> 2) 这条边, 总容量为5, 当前流量为3. 所以在残量图中: (1 -> 2) 的最大容量为 5 − 3 = 2 5-3=2 53=2, 反向边(2 - > 1)的容量为 3 3 3.

数学表述为: 给定一个流网络 G = ( V , E ) G=(V, E) G=(V,E) 以及其上的流 f f f, 残量图 G f Gf Gf 包含了原网络中的每条边, 同时也包括了反向边. 对于原网络中的每条边 ( u , v ) (u, v) (u,v), 如果当前流 f ( u , v ) f(u, v) f(u,v) 小于容量 c ( u , v ) c(u, v) c(u,v), 则在残量图中存在一条从 u u u v v v 的边, 容量为 c f ( u , v ) = c ( u , v ) − f ( u , v ) c_f(u, v) = c(u, v) - f(u, v) cf(u,v)=c(u,v)f(u,v); 同时, 如果 f ( u , v ) > 0 f(u, v) > 0 f(u,v)>0, 则在残量图中也存在一条从 v v v u u u 的边, 容量为 c f ( v , u ) = f ( u , v ) c_f(v, u) = f(u, v) cf(v,u)=f(u,v).

增广路径

在残量图中, 从源 s 到汇 t 的一条路径被称为增广路径, 如果这条路径上所有的边都具有正容量. 沿着这样的路径可以增加流值, 增加量由路径上最小剩余容量决定.

继续以上图为例, 我们可以在参量图中寻找从源点到汇点的路径. 如图, 我们发现了一条:

增广路径

可以看到这条路径可以通过的最大流量为2. 所以我们可以获得2的增量. 对应的流量图为右侧图所示.

Edmonds-Karp 算法

Edmonds-Karp算法是Ford-Fulkerson方法的一种实现, 用于计算网络流图中的最大流. 它特别之处在于使用广度优先搜索(BFS)来寻找增广路径, 即从源节点到汇节点的最短路径(这里的最短是指路径上的边数最少). 这保证了算法的效率和稳定性, 并且使得其时间复杂度为O(VE^2), 其中V是网络中的顶点数, E是边数.

算法步骤

  1. 初始化: 开始时, 所有边的流量都设为0.
  2. 寻找增广路径: 通过BFS寻找从源到汇的最短路径, 这条路径上所有的边必须有剩余容量(即实际容量大于当前流量).
  3. 更新流量: 一旦找到增广路径, 就沿着这条路径增加流量, 直到无法再找到增广路径为止.
  4. 终止条件: 当没有从源到汇的增广路径时, 算法终止, 此时的流量即为最大流.

Edmonds-Karp算法相比于其他实现Ford-Fulkerson方法的变种更加稳定, 因为它总是选择最短的增广路径, 这避免了某些情况下可能出现的低效行为. 此外, 由于其清晰的步骤和逻辑, 它也是学习网络流问题的良好起点.

代码实现

构建残量图
void BuildResidualGraph() {
  residual_graph_.Reset(graph_.V(), graph_.Directed(), graph_.Weighted());
  for (Vertex u = 0; u < graph_.V(); u++) {
    for (Vertex v : graph_.Adj(u)) {
      residual_graph_.AddEdge(u, v, graph_.GetWeight(u, v));
      residual_graph_.AddEdge(v, u, 0);
    }
  }
}
寻找增广路径
std::vector<WeightedEdge> FindArgumentPath(const AdjList& graph, unsigned src,
                                            unsigned dst) {
  std::vector<unsigned> parent(graph.V(), UINT_MAX);
  std::vector<bool> visited(graph.V(), false);

  std::queue<unsigned> q;
  q.push(src);
  while (!q.empty()) {
    auto curr = q.front();
    q.pop();

    if (curr == dst) break;
    if (visited[curr]) continue;
    visited[curr] = true;

    for (auto w : graph.Adj(curr)) {
      if (visited[w]) continue;
      if (graph.GetWeight(curr, w) <= 0) continue;
      parent[w] = curr;
      q.push(w);
    }
  }
  std::vector<WeightedEdge> path;
  if (parent[dst] == UINT_MAX) return path;
  int curr = dst;
  while (parent[curr] != src) {
    auto begin = parent[curr];
    auto end = curr;
    auto weight = graph.GetWeight(begin, end);
    path.emplace_back(begin, end, weight);
    curr = begin;
  }
  path.emplace_back(src, curr, graph.GetWeight(src, curr));
  std::reverse(path.begin(), path.end());
  return path;
}
Edmonds-Karp算法主程序
int MaxFlow(unsigned source, unsigned sink) {
  BuildResidualGraph();

  int max_flow = 0;
  while (true) {
    auto path = FindArgumentPath(residual_graph_, source, sink);
    fmt::println("path: {}\n", fmt::join(path, ","));

    if (path.empty()) break;
    auto it = std::min_element(path.begin(), path.end(),
                                [](const auto& lhs, const auto& rhs) {
                                  return std::get<2>(lhs) < std::get<2>(rhs);
                                });
    auto flow = std::get<2>(*it);
    max_flow += flow;

    for (auto& [u, v, w] : path) {
      residual_graph_.UpdateWeight(u, v, -flow);
      residual_graph_.UpdateWeight(v, u, flow);
    }
  }
  return max_flow;
}

完整代码请参考: EdmondsKarp.ixx

Edmonds-Karp Demo
std::vector<graph::WeightedEdge> edges = {
    std::make_tuple(0, 1, 3), std::make_tuple(0, 2, 2),
    std::make_tuple(1, 2, 5), std::make_tuple(1, 3, 2),
    std::make_tuple(2, 3, 3),
};
graph::AdjList wg(4, edges, true);
graph::EdmondsKarp ek(wg);
auto len = ek.MaxFlow(0, 3);
std::cout << "max flow: " << len << "\n";
std::cout << ek.ResidualGraph();

完整代码参考: MaxFlowDemo.cpp

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/979913.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python的那些事第三十六篇:基于 Vega 和 Vega-Lite 的数据可视化解决方案,Altair 声明式可视化库

Altair 声明式可视化库:基于 Vega 和 Vega-Lite 的数据可视化解决方案 摘要 在数据科学和分析领域,有效的数据可视化是理解数据、发现模式和传达见解的关键。Python 作为数据科学的主要编程语言之一,提供了多种数据可视化库。其中,Altair 是一个基于 Vega 和 Vega-Lite 的…

文件描述符与重定向

1. open系统调用 在 Linux 中, open() 系统调用用于打开一个文件或设备&#xff0c;并返回一个文件描述符&#xff0c;通过该描述符可以进行文件读写操作。open() 可以用于创建新文件或打开已存在的文件&#xff0c;具体行为取决于传递给它的参数。 需要包含的头文件&#xf…

【WPF】绑定报错:双向绑定需要 Path 或 XPath

背景 最开始使用的是 TextBlock: <ItemsControl ItemsSource"{Binding CameraList}"><ItemsControl.ItemsPanel><ItemsPanelTemplate><StackPanel Orientation"Horizontal"/></ItemsPanelTemplate></ItemsControl.Item…

【Linux高级IO】五种IO模型 多路转接(select)

目录 1. 五种IO模型 1.1 阻塞式IO 1.2 非阻塞IO 1.3 信号驱动IO 1.4 多路转接 1.5 异步IO 2. 同步通信与异步通信 3. 多路转接 3.1 select 总结 1. 五种IO模型 1.1 阻塞式IO 阻塞式IO最为常见&#xff0c;在内核将数据准备好之前, 系统调用会一直等待&#xff0c;所有的…

Linux服务升级:Almalinux 升级 DeepSeek-R1

目录 一、实验 1.环境 2.Almalinux 部署 Ollama 3.Almalinux 升级 DeepSeek-R1 4.Almalinux 部署 docker 5. docker 部署 DeepSeek-R1 6.Almalinux 部署 Cpolar (内网穿透) 7.使用cpolar内网穿透 二、问题 1.构建容器失败 一、实验 1.环境 &#xff08;1&#xff09…

深度剖析数据分析职业成长阶梯

一、数据分析岗位剖析 目前&#xff0c;数据分析领域主要有以下几类岗位&#xff1a;业务数据分析师、商业数据分析师、数据运营、数据产品经理、数据工程师、数据科学家等&#xff0c;按照工作侧重点不同&#xff0c;本文将上述岗位分为偏业务和偏技术两大类&#xff0c;并对…

CosyVoice2整合包 特殊声音标记,声音克隆更逼真,新增批量生成

新增批量生成,可用于制作直播话术音频 特殊声音标记 符号示例1_语气加强<strong> </strong>每天都<strong>付出</strong>和<strong>精进</strong>&#xff0c;才能达到巅峰。2_呼吸声[breath][breath] 吸气,[breath] 呼气! [breath] 吸,[b…

vector习题

完数和盈数 题目 完数VS盈数_牛客题霸_牛客网 一个数如果恰好等于它的各因子(该数本身除外)之和&#xff0c;如&#xff1a;6321。则称其为“完数”&#xff1b;若因子之和大于该数&#xff0c;则称其为“盈数”。 求出2到60之间所有“完数”和“盈数”。 输入描述&#xff…

如何保证 Redis 缓存和数据库的一致性?

如何保证 Redis 缓存和数据库的一致性&#xff1f; 1. 问题出现场景 先修改数据库&#xff0c;再删除缓存 删除数据库数据成功了&#xff0c;但是删除缓存却失败了&#xff0c;缓存中仍保留的是旧数据 先删除缓存&#xff0c;再删除数据库 如果 Redis 缓存删除成功后&#xf…

信刻光盘安全隔离与信息交换系统让“数据摆渡”安全高效

随着数据传输、存储及信息技术的飞速发展&#xff0c;信息安全保护已成为重中之重。各安全领域对跨网数据交互的需求日益迫切&#xff0c;数据传输的安全可靠性成为不可忽视的关键。为满足业务需求并遵守保密规范&#xff0c;针对于涉及重要秘密信息&#xff0c;需做到安全的物…

网络原理--TCP/IP(2)

我们在之前已经介绍到TCP协议的核心机制二,接下来我们将继续介绍其他的核心机制。 核心机制三:连接管理 即建立连接,断开连接,在正常情况下,TCP要经过三次握⼿建⽴连接,四次挥⼿断开连接。 建立连接:TCP是通过“三次握手” 在生活中的握手就是打招呼,,但握手操作没有…

Windows PicPick Professional-v7.3.2-中文版

Windows PicPick Professional-中文版 链接&#xff1a;https://pan.xunlei.com/s/VOKGwGVGWUDl7L8cW4D1A1W4A1?pwdw5qz# - 更新了中文翻译&#xff0c;默认取消检测升级&#xff0c;删除多国语言

校园二手交易微信小程序的设计与实现(论文源码调试讲解)

第4章 系统设计 一个成功设计的系统在内容上必定是丰富的&#xff0c;在系统外观或系统功能上必定是对用户友好的。所以为了提升系统的价值&#xff0c;吸引更多的访问者访问系统&#xff0c;以及让来访用户可以花费更多时间停留在系统上&#xff0c;则表明该系统设计得比较专…

数据结构之各类排序算法代码及其详解

1. 排序的概念 排序是一种常见的算法概念&#xff0c;用于将一组数据按照特定的顺序进行排列。排序算法的目的是将一组数据按照递增或递减的顺序重新排列。常见的排序算法包括冒泡排序、插入排序、选择排序、快速排序、归并排序等。排序算法的选择通常取决于数据规模、数据分布…

6.6.6 嵌入式SQL

文章目录 2个核心问题识别SQL语句主语言和SQL通信完整导图 2个核心问题 SQL语句嵌入高级语言需要解决的2个核心问题是&#xff1a;如何识别嵌入语句&#xff1f;如何让主语言&#xff08;比如C,C语言&#xff09;和SQL通信&#xff1f; 识别SQL语句 为了识别主语言中嵌入的SQL…

keil主题(vscode风格)

#修改global.prop文件&#xff0c;重新打开keil即可 # Keil uVision Global Properties File # This file is used to customize the appearance of the editor# Editor Font editor.font.nameConsolas editor.font.size10 editor.font.style0# Editor Colors editor.backgro…

医疗AR眼镜:FPC如何赋能科技医疗的未来之眼?【新立电子】

随着科技的飞速发展&#xff0c;增强现实&#xff08;AR&#xff09;技术在医疗领域的应用逐渐成为焦点。医疗AR眼镜作为一种前沿的智能设备&#xff0c;正在为医疗行业带来深刻的变革。它不仅能够提升医生的工作效率&#xff0c;还能改善患者的就医体验&#xff0c;成为医疗科…

【异地访问本地DeepSeek】Flask+内网穿透,轻松实现本地DeepSeek的远程访问

写在前面&#xff1a;本博客仅作记录学习之用&#xff0c;部分图片来自网络&#xff0c;如需引用请注明出处&#xff0c;同时如有侵犯您的权益&#xff0c;请联系删除&#xff01; 文章目录 前言依赖Flask构建本地网页访问LM Studio 开启网址访问DeepSeek 调用模板Flask 访问本…

GPIO(嵌入式学习)

GPIO 通用输入输出口&#xff1a; 可分为八种输入输出模式 输出模式 下端可控制端口输出高低电平&#xff0c;用以驱动LED&#xff0c;控制蜂鸣器&#xff0c;模拟通信协议输出时序 输入模式 读取高低电平或电压&#xff0c;用与读取按键输入&#xff0c;外界模块电平信号…

AI助理精准匹配------助力快速搭建Stable Difussion图像生成应用

AI助理精准匹配------助力快速搭建Stable Difussion图像生成应用 背景信息搭建Stable Difussion图像生成应用释放资源 背景信息 过去你在阿里云社区搭建Stable Difussion图像生成应用&#xff0c;你可能还需要去在线实验室或者是官方文档去查找部署步骤&#xff0c;找到部署步…