网络流算法: Dinic算法
图论相关帖子
- 基本概念
- 图的表示: 邻接矩阵和邻接表
- 图的遍历: 深度优先与广度优先
- 拓扑排序
- 图的最短路径:Dijkstra算法和Bellman-Ford算法
- 最小生成树
- 二分图
- 多源最短路径
- 强连通分量
- 欧拉回路和汉密尔顿回路
- 网络流算法: Edmonds-Karp算法
- 网络流算法: Dinic算法
环境要求
本文所用样例在Windows 11
以及Ubuntu 24.04
上面编译通过.
- Windows: 使用[Visual Studio],
- Ubuntu: 使用 Clang 18.1.3. (Ubuntu 24.04 系统安装版本)
- GCC 无法编译直接本项目代码, 因为本文代码使用了 C++20 Module, 而 GCC 对此支持不完整.
intro
Dinic 算法是一种用于解决最大流问题的高效算法, 它基于 Ford-Fulkerson 方法, 并通过引入层次图(level graph)的概念来加速寻找增广路径的过程. 我们先从层次图的概念开始.
层次图(Level Graph)
层次图是一个特殊的网络, 它通过引入层次值来表示节点之间的距离. 层次值从源点开始, 按照广度优先搜索(BFS)的顺序进行更新, 直到所有节点都被更新.
构建层次图的过程主要是通过从源点开始进行一次广度优先搜索(BFS), 根据节点到源点的距离来为每个节点分配层次值(level). 以下是构建层次图的具体步骤:
构建层次图的步骤
-
初始化:
- 设定源点的层次值为 0.
- 创建一个队列, 并将源点加入队列中.
-
执行广度优先搜索(BFS):
- 从队列中取出一个节点 u u u.
- 对于 u u u 的所有邻接节点 v v v, 如果边 ( u , v ) (u, v) (u,v) 在残量图中仍有剩余容量(即该边未被完全利用), 并且 v v v 还未被访问过(或其层次值尚未确定), 则:
- 设置 v v v 的层次值为 u u u 的层次值加 1, 表示 v v v 比 u u u “远” 一个单位距离.
- 将 v v v 加入队列中, 以便后续处理 v v v 的邻接节点.
- 如果到达汇点, 则停止搜索; 否则继续直到队列为空.
-
结束条件:
- 当队列为空时, 说明已经遍历了所有可到达的节点, 并且为这些节点分配了层次值. 此时, 如果汇点没有被访问到(即它没有层次值), 意味着无法再找到从源点到汇点的任何路径, 算法可以终止.
- 如果汇点有层次值, 则层次图构建完成, 可以进行下一步的深度优先搜索(DFS)以寻找增广路径.
层次图构建样例
-
初始化. 给定一个原始图, 右侧是它的层次图初始状态.
-
从源点(
0
)开始出发, 第一层接触到1
和3
. 将边(0, 1)
,(0, 3)
加入到层次图中. -
接下来从
1
和3
开始, 第二层接触到2
和4
. 将边(1, 2)
,(3, 2)
,(3, 4)
加入到层次图中. -
最后从
2
和4
开始, 第三层接触到5
, 将边(2,5)
,(4,5)
加入到层次图中.
-
由于
5
是汇点, 所以算法结束. 最后的结果如下:
层次图的作用
- 限制搜索范围: 层次图只包含那些按照一定顺序(即按层次值从小到大)排列的节点和边, 这使得在寻找增广路径时只需考虑特定的边集, 减少了不必要的搜索.
- 加快增广路径的查找速度: 由于 DFS 仅沿着层次图中的边进行搜索, 这样可以在较短的时间内找到一条或多条增广路径, 从而提高整个算法的效率.
通过以上步骤, Dinic 算法能够有效地构建层次图, 并利用这个结构快速找到增广路径, 最终求解最大流问题. 这种方法不仅提高了算法的性能, 而且使其实现更加直观易懂.
Dinic 算法步骤
Dinic 算法步骤
-
构建层次图: 从源点开始进行广度优先搜索(BFS), 为每个节点分配一个层次值(level), 这个值表示该节点到源点的距离. 只有当流量可以通过一系列边从一个较低层次的节点流向较高层次的节点时, 这条路径才会被纳入层次图中.
-
寻找增广路径: 使用深度优先搜索(DFS)在层次图中寻找从源点到汇点的增广路径. 由于层次图的性质, DFS 可以更高效地找到这些路径.
-
更新残量图: 一旦找到了一条增广路径, 就沿着这条路径调整流量, 同时更新相应的残量图. 这包括增加正向边的流量并减少反向边的容量.
-
重复步骤: 重复上述步骤, 直到无法再找到从源点到汇点的增广路径为止.
Dinic 算法样例
-
初始化: 给定一个原始图如图左侧所示, 如果我们省去容量为 0 的边, 那么左侧图可以看做是一个残量图, 右侧是它的层次图初始状态.
-
利用 DFS 寻找一条增广路径, 我们找到的了一条, 如图所示. 它的容量为 8, 接着我们更新残量图, 得到如图右边所示的图. 注意: 此时
1 -> 2
的边容量为 0, 故而省去. -
我们继续从残量图获取层级图. 如下:
-
继续寻找增广路径, 找到一条, 如图所示, 容量为 3. 它的容量为 3, 接着我们更新残量图, 得到如图右边所示的图. 注意: 此时
3 -> 4
的边容量为 0, 故而省去. -
我们继续从残量图获取层级图. 如下:
-
继续寻找增广路径, 找到一条, 如图所示, 容量为 1. 接着我们更新残量图, 得到如图右边所示的图. 注意: 此时
3 -> 2
的边容量为 0, 故而省去. -
我们继续从残量图获取层级图. 如下:
-
继续寻找增广路径, 找不到了, 所以算法结束.
特点和优势:
- 时间复杂度: Dinic 算法的时间复杂度为 O ( V 2 E ) O(V^2 E) O(V2E), 其中 V V V 是顶点数, E E E 是边数. 对于稠密图, 其性能优于 Edmonds-Karp 算法.
- 阻塞流: Dinic 算法每次迭代都会尝试在当前层次图中找到一个"阻塞流", 即无法再在这个层次图中找到任何增广路径. 这种策略使得算法效率更高.
- 应用广泛: 除了用于计算最大流之外, Dinic 算法还常用于解决二分图匹配等问题.
C++ 代码实现
class Dinic {public:explicit Dinic(const AdjList& graph): graph_(graph), residual_graph_(graph.V(), true, true) {BuildResidualGraph();}int MaxFlow(int src, int dst) {int max_flow = 0;while (true) {auto level = BuildLevelGraph(src);auto path = FindArgumentPath(level, src, dst);fmt::println("current path: {}", 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);});int flow = std::get<2>(*it);if (flow <= 0) {break;}max_flow += flow;for (auto& [u, v, w] : path) {residual_graph_.UpdateWeight(u, v, -flow);residual_graph_.UpdateWeight(v, u, flow);}}return max_flow;}void BuildResidualGraph() {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);}}}AdjList BuildLevelGraph(unsigned src) {AdjList g(graph_.V(), true, true);std::queue<unsigned> q;q.push(src);while (!q.empty()) {auto u = q.front();q.pop();for (auto v : residual_graph_.Adj(u)) {int w = residual_graph_.GetWeight(u, v);if (w <= 0 || g.HasEdge(u, v)) {continue;}g.AddEdge(u, v, w);q.push(v);}}return g;}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;}private:const AdjList& graph_;AdjList residual_graph_;
};
代码源文件链接在此: Dinic.ixx
相关文章:

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

【Godot4.3】自定义简易菜单栏节点ETDMenuBar
概述 Godot中的菜单创建是一个复杂的灾难性工作,往往无从下手,我也是不止一次尝试简化菜单的创建。 从自己去年的发明“简易树形数据”用于简化Tree控件获得灵感,于是尝试编写了用于表示菜单数据的EasyMenuData类,以及对应的纯文…...

如何杀死僵尸进程?没有那个进程?
在题主跑代码的时候遇到了这样一种很奇怪的问题: 可以看到显卡0没有跑任何程序但是还是被占据着大量显存,这种进程称为“僵尸进程”,并且当我想kill它的时候,出现下面这种情况: 查过各种资料,最后我的解决…...

Solana 核心概念全解析:账户、交易、合约与租约,高流量区块链技术揭秘!
目录 1.Solana 核心概念简述 1.1. 账户(Account) 1.2. 交易(Transaction) 1.3. 交易指令(Instruction) 1.4. SPL 代币 1.5. 合约(Program) 1.6. 租约(Rent&#x…...

Leetcode-853. Car Fleet [C++][Java]
目录 一、题目描述 二、解题思路 Leetcode-853. Car Fleethttps://leetcode.com/problems/car-fleet/description/ 一、题目描述 There are n cars at given miles away from the starting mile 0, traveling to reach the mile target. You are given two integer array …...

012 rocketmq事务消息
文章目录 事务消息概念介绍交互流程事务消息原理TransactionListener接⼝TransactionProducer.javaTransactionConsumer.java 事务消息 内置topic中的消息对消费者不可见 本地事务mq消息事务消息 消息队列 RocketMQ 版提供的分布式事务消息适⽤于所有对数据最终⼀致性有强需求…...
ChatGPT与DeepSeek:开源与闭源的AI模型之争
目录 一、模型架构与技术原理 二、性能能力与应用场景 三、用户体验与部署灵活性 四、成本与商业模式 五、未来展望与市场影响 六、总结 随着人工智能技术的飞速发展,ChatGPT和DeepSeek作为两大领先的AI语言模型,成为了行业内外关注的焦点。它们在…...

Ollama的底层实现原理分析
一、背景 Ollama我们可以很方便的对DeepSeek等开源大模型进行部署,几条命令便能部署一个本地大模型服务,降低了非专业大模型开发者的门槛。 我们从中可以看到类似Docker的影子,ollama run 、ollama list等等,拉取对应大模型镜像&a…...
nginx 动态计算拦截非法访问ip
需求:在Nginx上实现一个动态拦截IP的方法,具体是当某个IP在1分钟内访问超过60次时,将其加入Redis并拦截,拦截时间默认1天。 技术选型:使用NginxLuaRedis的方法。这种方案通过Lua脚本在Nginx处理请求时检查Redis中的黑…...

商业秘密维权有哪些成本开支?
企业商业秘密百问百答之六十三:商业秘密维权费用项目有哪些? 在商业秘密维权过程中,原告可能需要支付多种费用,一般费用项目包括: 1、诉讼费。诉讼费是向法院支付的费用,包括起诉费、案件受理费等。这些费…...

使用UA-SPEECH和TORGO数据库验证自动构音障碍语音分类方法
使用UA-SPEECH和TORGO数据库验证自动构音障碍语音分类方法 引言 原文:On using the UA-Speech and TORGO databases to validate automatic dysarthric speech classification approaches 构音障碍简介 构音障碍是一种由于脑损伤或神经疾病(如脑瘫、肌萎缩侧索硬化症、帕金森…...
WebSocketHandler 是 Spring Framework 中用于处理 WebSocket 通信的接口
WebSocketHandler 是 Spring Framework 中用于处理 WebSocket 通信的接口,其主要作用是定义了如何处理 WebSocket 的各种事件和消息。以下是 WebSocketHandler 的主要作用和功能: ### 1. 处理 WebSocket 生命周期事件 WebSocketHandler 定义了多个方法来…...

Pikachu
一、网站搭建 同样的,先下载安装好phpstudy 然后启动Apache和Mysql 然后下载pikachu,解压到phpstudy文件夹下的www文件 然后用vscode打开pikachu中www文件夹下inc中的config.inc.php 将账户和密码改为和phpstudy中的一致(默认都是root&…...
如何使用 Jenkins 实现 CI/CD 流水线:从零开始搭建自动化部署流程
如何使用 Jenkins 实现 CI/CD 流水线:从零开始搭建自动化部署流程 在软件开发过程中,持续集成(CI)和持续交付(CD)已经成为现代开发和运维的标准实践。随着代码的迭代越来越频繁,传统的手动部署方式不仅低效,而且容易出错。为了提高开发效率和代码质量,Jenkins作为一款…...

Vue.js 学习笔记
文章目录 前言一、Vue.js 基础概念1.1 Vue.js 简介1.2 Vue.js 的特点1.3 Vue.js 基础示例 二、Vue.js 常用指令2.1 双向数据绑定(v-model)2.2 条件渲染(v-if 和 v-show)2.3 列表渲染(v-for)2.4 事件处理&am…...
数据存储:一文掌握RabbitMQ的详细使用
文章目录 一、RabbitMQ简介二、RabbitMQ的概述2.1 基本概念2.2 实际应用场景三、RabbitMQ的安装与配置3.1 安装RabbitMQ3.2 启用管理插件四、使用Python操作RabbitMQ4.1 安装Pika库4.2 生产者示例4.3 消费者示例4.4 发布/订阅模式示例五、RabbitMQ的高级特性5.1 消息持久化5.2 …...

辛格迪客户案例 | 祐儿医药科技GMP培训管理(TMS)项目
01 项目背景:顺应行业趋势,弥补管理短板 随着医药科技行业的快速发展,相关法规和标准不断更新,对企业的质量管理和人员培训提出了更高要求。祐儿医药科技有限公司(以下简称“祐儿医药”)作为一家专注于创新…...

FreeRtos实时系统: 十六.tickless低功耗模式
FreeRtos实时系统: 十六.tickless低功耗模式 一.tickless低功耗模式简介二.tickless模式详解三.tickless模式相关配置项四.tickless低功耗模式实验五.课堂总结 一.tickless低功耗模式简介 STM32低功耗模式: 二.tickless模式详解 为了可以降低功耗,又不…...

CSDN博客:Markdown编辑语法教程总结教程(上)
❤个人主页:折枝寄北的博客 Markdown编辑语法教程总结 前言1. CSDN Markdown编辑器功能简介1.1 基础操作界面1.2 创作助手和语法说明 2. Markdown编辑器语法2.1 目录2.2 标题2.2.1 标题级别设置2.2.2 标题居中 3. 文本样式3.1 强调文本(斜体)…...

多个pdf合并成一个pdf的方法
将多个PDF文件合并优点: 能更容易地对其进行归档和备份.打印时可以选择双面打印,减少纸张的浪费。比如把住宿发票以及滴滴发票、行程单等生成一个pdf,双面打印或者无纸化办公情况下直接发送给财务进行存档。 方法: 利用PDF24 Tools网站 …...
谷歌浏览器插件
项目中有时候会用到插件 sync-cookie-extension1.0.0:开发环境同步测试 cookie 至 localhost,便于本地请求服务携带 cookie 参考地址:https://juejin.cn/post/7139354571712757767 里面有源码下载下来,加在到扩展即可使用FeHelp…...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...

Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...

10-Oracle 23 ai Vector Search 概述和参数
一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI,使用客户端或是内部自己搭建集成大模型的终端,加速与大型语言模型(LLM)的结合,同时使用检索增强生成(Retrieval Augmented Generation &#…...

莫兰迪高级灰总结计划简约商务通用PPT模版
莫兰迪高级灰总结计划简约商务通用PPT模版,莫兰迪调色板清新简约工作汇报PPT模版,莫兰迪时尚风极简设计PPT模版,大学生毕业论文答辩PPT模版,莫兰迪配色总结计划简约商务通用PPT模版,莫兰迪商务汇报PPT模版,…...

如何把工业通信协议转换成http websocket
1.现状 工业通信协议多数工作在边缘设备上,比如:PLC、IOT盒子等。上层业务系统需要根据不同的工业协议做对应开发,当设备上用的是modbus从站时,采集设备数据需要开发modbus主站;当设备上用的是西门子PN协议时…...

Linux操作系统共享Windows操作系统的文件
目录 一、共享文件 二、挂载 一、共享文件 点击虚拟机选项-设置 点击选项,设置文件夹共享为总是启用,点击添加,可添加需要共享的文件夹 查询是否共享成功 ls /mnt/hgfs 如果显示Download(这是我共享的文件夹)&…...
【Pandas】pandas DataFrame dropna
Pandas2.2 DataFrame Missing data handling 方法描述DataFrame.fillna([value, method, axis, …])用于填充 DataFrame 中的缺失值(NaN)DataFrame.backfill(*[, axis, inplace, …])用于**使用后向填充(即“下一个有效观测值”)…...

如何使用CodeRider插件在IDEA中生成代码
一、环境搭建与插件安装 1.1 环境准备 名称要求说明操作系统Windows 11JetBrains IDEIntelliJ IDEA 2025.1.1.1 (Community Edition)硬件配置推荐16GB内存50GB磁盘空间 1.2 插件安装流程 步骤1:市场安装 打开IDEA,进入File → Settings → Plugins搜…...

MySQL 数据库深度剖析:事务、SQL 优化、索引与 Buffer Pool
在当今数据驱动的时代,数据库作为数据存储与管理的核心,其性能与可靠性至关重要。MySQL 作为一款广泛使用的开源数据库,在众多应用场景中发挥着关键作用。在这篇博客中,我将围绕 MySQL 数据库的核心知识展开,涵盖事务及…...