当前位置: 首页 > news >正文

拓扑排序在实际开发中的应用

1. 拓扑排序说明

简单解释:针对于有向无环图(DAG),给出一个可行的节点排序,使节点之间的依赖关系不冲突。

复杂解释:自行搜索相关资料。

本次应用中的解释:给出一个可行的计算顺序,使得每个字段计算时,所需的变量已经完成了计算。

2. 业务场景

在开发的报表数据管理系统中,系统的业务流程如下:

① 先从目标数据库中抓取数据值,例如目标系统为物流系统,抓取其中物流订单的相关数据,送货地区、货物体积、数量、型号等数据。

② 对数据进行一定的运算逻辑,如计算总费用= 运费+卸货费

问题难点:

系统在计算的过程中,存在多级计算,如下图中的单价、运费、卸货费、总费用都需要进行计算,如何确定每个字段的计算顺序,保证总费用在计算时,运费已经完成计算了。即系统需要将运费的计算先排在总费用的计算前面。这个时候就可以应用到拓扑排序。

如果系统不去进行这么一个排序的话,用户在添加公式的时候,需要考虑字段之间的顺序是否符合先后顺序, 如果出现了计算总费用时,运费这个字段还没有计算完成的情况,系统会出现报错,用户需要对错误进行排查,导致用户使用体验非常差。

在这里插入图片描述

3. 实际应用代码

kahn算法说明:

  • 维护一个入度为0的队列
  • 先找到所有入度为0的点,并从图中移除,加入到入度为0的队列中,即setOfZeroIndegree
  • 循环处理setOfZeroIndegree队列中的所有点,将点向外指出的边进行删除,A->B,A的入度为0 ,B的入度为1,删除掉A点和B点的边后,B点的入度为0,这个时候B点也可以加入setOfZeroIndegree队列中
  • 挡setOfZeroIndegree队列为空时,判断图中是否还有边没有删除干净,如果没有删除干净,则当前图为有环图,无法得到可行解。如果删除干净了,表示给出的图是有可行解的。

拓扑排序工具类

/*** @author kstar* @date 2024/10/16* @description* 操作方法见main方法,有详细的操作流程*/import java.util.*;public class GraphUtils {// 定义点的数据结构public static class Node {public Object val;public int pathIn = 0; // 入链路数量public Node(Object val) {this.val = val;}}/*** 拓扑图类*/public static class Graph {// 图中节点的集合public Set<Node> vertexSet = new HashSet<Node>();// 相邻的节点,纪录边public Map<Node, Set<Node>> adjaNode = new HashMap<Node, Set<Node>>();// 将节点加入图中public boolean addNode(Node start, Node end) {// 判断节点集合中是否有这两个节点,没有的话就加入到节点集合if (!vertexSet.contains(start)) {vertexSet.add(start);}if (!vertexSet.contains(end)) {vertexSet.add(end);}// 如果图中已经存在该边,则不添加if (adjaNode.containsKey(start)&& adjaNode.get(start).contains(end)) {return false;}// 判断当前节点是否有边的map,有的话可以直接加入,没有的话新建一个setif (adjaNode.containsKey(start)) {adjaNode.get(start).add(end);} else {Set<Node> temp = new HashSet<Node>();temp.add(end);adjaNode.put(start, temp);}// 被指向的节点入度+1end.pathIn++;return true;}}/**Kahn算法*  0. 维护一个入度为0的队列*  1. 先找到所有入度为0的点,并从图中移除,加入到入度为0的集合中*  2. 循环处理setOfZeroIndegree集合中的所有点,*/public static class KahnTopo {private List<Node> result; // 用来存储结果集,结果数据集就是最终的排序结果private Queue<Node> setOfZeroIndegree; // 用来存储入度为0的顶点private Graph graph;//构造函数,初始化public KahnTopo(Graph di) {this.graph = di;this.result = new ArrayList<Node>();this.setOfZeroIndegree = new LinkedList<Node>();// 对入度为0的集合进行初始化for(Node iterator : this.graph.vertexSet){if(iterator.pathIn == 0){this.setOfZeroIndegree.add(iterator);}}}//拓扑排序处理过程public void process() {while (!setOfZeroIndegree.isEmpty()) {Node v = setOfZeroIndegree.poll();// 将当前顶点添加到结果集中result.add(v);if (this.graph.adjaNode.get(v)==null){this.graph.vertexSet.remove(v);continue;}if(this.graph.adjaNode.keySet().isEmpty()){return;}// 遍历由v引出的所有边for (Node w : this.graph.adjaNode.get(v) ) {// 将该边从图中移除,通过减少边的数量来表示w.pathIn--;if (0 == w.pathIn) // 如果入度为0,那么加入入度为0的集合{setOfZeroIndegree.add(w);}}this.graph.vertexSet.remove(v);this.graph.adjaNode.remove(v);}// 如果此时图中还存在边,那么说明图中含有环路if (!this.graph.vertexSet.isEmpty()) {List<String> errNode = new ArrayList<>();for (Node node : this.graph.vertexSet) {errNode.add(node.val.toString());}throw new IllegalArgumentException("当前参数存在循环依赖,请检查:"+errNode);}}//结果集public Iterable<Node> getResult() {return result;}}//测试方法public static void main(String[] args) {// 添加点Node A = new Node("A");Node B = new Node("B");Node C = new Node("C");Node D = new Node("D");Node E = new Node("E");Node F = new Node("F");// 添加边Graph graph = new Graph();graph.addNode(A, B);graph.addNode(B, C);graph.addNode(B, D);graph.addNode(D, C);graph.addNode(E, C);graph.addNode(C, F);KahnTopo topo = new KahnTopo(graph);topo.process();for(Node temp : topo.getResult()){System.out.print(temp.val.toString() + "-->");}}
}

实际应用代码

// 在实际业务场景中的应用,仅做说明演示,直接复制过去无法使用,要用的话用上面的代码的main方法即可测试
// 样例方法中,需要对CollectSchemeDetail这个类中进行排序,
// 排序时根据类中的getCollectResultCode构建图,将计算时需要的字段解析出来构建图
public void calculateOrder(String collectSchemeCode){List<CollectSchemeDetail> collectSchemeDetails = listByCode(collectSchemeCode);Map<String, GraphUtils.Node> nodeMap = new HashMap<>();// 循环遍历对象数组中的值,取出其中的点和图的关系for (CollectSchemeDetail schemeDetail : collectSchemeDetails) {GraphUtils.Node node = new GraphUtils.Node(schemeDetail.getCollectResultCode());nodeMap.put(schemeDetail.getCollectResultCode(),node);}GraphUtils.Graph graph = new GraphUtils.Graph();for (CollectSchemeDetail schemeDetail : collectSchemeDetails) {if (schemeDetail.getCollectType().equals(CollectSchemeDetail.CollectTypeEnum.EQUATION)) {if (schemeDetail.getExpression()==null||!schemeDetail.getExpression().contains("$")){throw new GlobalException("公式:["+schemeDetail.getCollectResultName()+"]中没有设置表达式,请先设置表达式后再添加");}// 公式解析,expression为公式,如A+B-C,// JEPUtils.getVariables的方法可以将公式中的有效字段解析出来List<String> variableList = JEPUtils.getVariables(schemeDetail.getExpression());for (String variable : variableList) {graph.addNode(nodeMap.get(variable), nodeMap.get(schemeDetail.getCollectResultCode()));}}}// 执行拓扑排序算法GraphUtils.KahnTopo topo = new GraphUtils.KahnTopo(graph);topo.process();Map<String,List<CollectSchemeDetail>> resultCodeSchemeDetailMap = collectSchemeDetails.stream().collect(Collectors.groupingBy(CollectSchemeDetail::getCollectResultCode));int i = 1;List<CollectSchemeDetail> needUpdateList = new ArrayList<>();// 循环拓扑排序的结果,对数据的计算顺序进行更新for(GraphUtils.Node temp : topo.getResult()){for (CollectSchemeDetail collectSchemeDetail : resultCodeSchemeDetailMap.get(temp.val.toString())) {collectSchemeDetail.setCalculateOrder(i);}needUpdateList.addAll(resultCodeSchemeDetailMap.get(temp.val.toString()));i++;}updateBatchById(needUpdateList);}

4. 解决问题

处理后字段的计算顺序按照拓扑排序给出的一个可行解进行排序,如在给出的样例图中,运费的计算需要先由地区计算出单价,再由单价*数量得到运费。那么地区、数量、体积的计算顺序应该是0,运费的计算顺序是1,卸货费用的计算顺序是2,总费用的计算顺序为3,总费用= 运费+卸货费用。

至此,用户可以随意的去修改字段之间的计算公式,而不需要考虑计算顺序出现错误。只有在设定的公式存在环路时,系统会返回报错信息,告知用户哪几个字段之间存在环路,让用户重新配置公式。

用户的使用体验得到了优化。

5. 相关链接

本样例说明源码开源在:
ruoyi-reoprt gitee仓库
ruoyi-report github仓库
欢迎大家到到项目中多给点star支持,对项目有建议或者有想要了解的欢迎一起讨论

相关文章:

拓扑排序在实际开发中的应用

1. 拓扑排序说明 简单解释&#xff1a;针对于有向无环图&#xff08;DAG&#xff09;&#xff0c;给出一个可行的节点排序&#xff0c;使节点之间的依赖关系不冲突。 复杂解释&#xff1a;自行搜索相关资料。 本次应用中的解释&#xff1a;给出一个可行的计算顺序&#xff0…...

【CTF-SHOW】Web入门 Web27-身份证日期爆破 【关于bp intruder使用--详记录】

1.点进去 是一个登录系统&#xff0c;有录取名单和学籍信息 发现通过姓名和身份证号可以进行录取查询&#xff0c;推测录取查询可能得到学生对应学号和密码&#xff0c;但是身份证号中的出生日期部分未知&#xff0c;所以可以进行爆破 2.打开bp抓包 这里注意抓的是学院录取查…...

Windows 添加右键以管理员身份运行 PowerShell

在 Windows 系统中添加一个右键菜单选项&#xff0c;以便可以使用管理员权限打开 PowerShell&#xff0c;可以通过编辑注册表来实现。 打开注册表编辑器&#xff1a; 按 Win R 打开运行对话框。输入 regedit 并按回车&#xff0c;这将打开注册表编辑器。 导航到文件夹背景键&…...

数学建模算法与应用 第15章 预测方法

目录 15.1 微分方程模型 Matlab代码示例&#xff1a;求解简单的微分方程 15.2 灰色预测模型&#xff08;GM&#xff09; Matlab代码示例&#xff1a;灰色预测模型 15.3 自回归模型&#xff08;AR&#xff09; Matlab代码示例&#xff1a;AR模型的预测 15.4 指数平滑法 M…...

HC32F460KETA PETB JATA 工业 自动化 电机

HC32F460 系列是基于 ARM Cortex-M4 32-bit RISC CPU&#xff0c;最高工作频率 200MHz 的高性能 MCU。Cortex-M4 内核集成了浮点运算单元&#xff08;FPU&#xff09;和 DSP&#xff0c;实现单精度浮点算术运算&#xff0c;支持 所有 ARM 单精度数据处理指令和数据类型&#xf…...

linux系统,不定时kernel bug :soft lockup的问题

这个问题困扰好久&#xff0c;机器经常不定时卡死&#xff0c;只能重启 后来检查是因为没有安装nvidia显卡驱动&#xff0c;或者更新到最新驱动 下载地址&#xff1a;驱动详情 禁止nouveau就可以了...

【C语言教程】【常用类库】(十四)可移植库 - <unistd.h> 和 <sys/types.h>

14. 可移植库 - <unistd.h> 和 <sys/types.h> UNIX和类UNIX系统上提供的一组头文件&#xff0c;其中<unistd.h>定义了POSIX操作系统API的访问点&#xff0c;而<sys/types.h>定义了许多基础数据类型。这些库在多种环境中增强了C程序的可移植性。 14.1…...

Java项目实战II基于Spring Boot的周边游平台设计与实现(源码+数据库+文档)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 随着人们生…...

远程控制软件哪个好用:4款主流的远程控制软件大点评,谁最给力?

战国时期&#xff0c;有百家争鸣&#xff0c;九流十家&#xff0c;争芳斗艳&#xff1b; 时至今日&#xff0c;科学技术突飞猛进、一日千里&#xff0c;各大远程控制软件更是佳丽三千、琳琅满目、各有千秋&#xff01; 这时&#xff0c;新的问题来了&#xff1a;远程控制软件哪…...

基于springboot实习管理系统

作者&#xff1a;计算机学长阿伟 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、ElementUI等&#xff0c;“文末源码”。 系统展示 【2024最新】基于JavaSpringBootVueMySQL的&#xff0c;前后端分离。 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;…...

(38)MATLAB分析带噪信号的频谱

文章目录 前言一、MATLAB仿真代码二、仿真结果画图总结 前言 本文给出带噪信号的时域和频域分析&#xff0c;指出频域分析在处理带噪信号时的优势。 首先使用MATLAB生成一段信号&#xff0c;并在信号上叠加高斯白噪声得到带噪信号&#xff0c;然后对带噪信号对其进行FFT变换&…...

多级缓存-案例导入说明

为了演示多级缓存,我们先导入一个商品管理的案例,其中包含商品的CRUD功能。我们将来会给查询商品添加多级缓存。 1.安装MySQL 后期做数据同步需要用到MySQL的主从功能,所以需要大家在虚拟机中,利用Docker来运行一个MySQL容器。 1.1.准备目录 为了方便后期配置MySQL,我们…...

基于Python的自然语言处理系列(31):SpaCy + Training Neural Network

1. 介绍 在自然语言处理的多个任务中,训练神经网络模型是一个至关重要的步骤,它能帮助我们实现更精准的模型预测。对于特定的任务,如命名实体识别(NER)或文本分类,使用自定义的训练数据对模型进行微调是提高模型表现的有效方式。在这篇文章中,我们将深入探讨如何从零开始…...

在 cPanel 中管理区域编辑权限

在 cPanel & WHM 60 版本中&#xff0c;cPanel 界面有四种不同方式编辑你的区域文件。简单 DNS 编辑器&#xff08;cPanel >> 域名 >> 简单 DNS 编辑器&#xff09;允许用户设置 A 记录和 CNAME 记录。高级 DNS 编辑器&#xff08;cPanel >> 域名 >&g…...

web前端网页用户注册页面

源码&#xff1a; <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>用户注册</title> </head> <body><form action"#" metho…...

问题记录-- 在 Vue2 中动态更新 Select 组件的选项

在 Vue2 中动态更新 Select 组件的选项 在 Vue 开发中&#xff0c;动态更新表单组件的选项是一个常见的需求。特别是在使用 select 组件时&#xff0c;如何确保选项能够实时反映数据的变化是一个值得关注的问题。本文将探讨如何通过方法获取 select 的 options 来解决这一问题…...

Opencv形态学的膨胀操作、开运算与闭运算、梯度运算、礼帽与黑帽操作

文章目录 一、膨胀操作二、开运算与闭运算三、梯度运算四、礼帽与黑帽操作 一、膨胀操作 膨胀操作也就是根据图片将边缘的一些细节给丰富&#xff0c;处理的程度取决于卷积核的大小还有膨胀次数。也就是腐蚀操作的相反操作&#xff08;腐蚀操作参考我的上一篇文章 点击跳转&am…...

keil 中添加gcc编译 stmf207

一、安装下载arm-gcc 编译器&#xff1a; 二、在keil中配置gcc&#xff1a; 三、配置工程选项 1.配置gcc编译规则&#xff1a; Misc Controls : -mcpucortex-m3 -mthumb -fdata-sections -ffunction-sections 注&#xff1a; 1.这里我用的cortex-m3&#xff0c;如果你是m4内核…...

BEV相关

1.deformable DETR是在DETR基础上做了什么 Deformable DETR 是对经典 DETR&#xff08;Detection Transformer&#xff09;进行的改进&#xff0c;旨在解决 DETR 训练速度慢、对大目标的定位不精确等问题。它主要在以下几个方面做了优化&#xff1a; 稀疏的多尺度注意力机制&a…...

nodepad++带时间段的关键字搜索筛选

10:11:[2-3][0-9].(com.asus.rogforum) 如图&#xff1a;冒号后面的[2-3]表示秒的十位20秒到30秒之间&#xff0c;如果想筛选多个则(com.asus.rogforum)中的多个关键字之间用|分隔...

Debian系统简介

目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版&#xff…...

【项目实战】通过多模态+LangGraph实现PPT生成助手

PPT自动生成系统 基于LangGraph的PPT自动生成系统&#xff0c;可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析&#xff1a;自动解析Markdown文档结构PPT模板分析&#xff1a;分析PPT模板的布局和风格智能布局决策&#xff1a;匹配内容与合适的PPT布局自动…...

浅谈不同二分算法的查找情况

二分算法原理比较简单&#xff0c;但是实际的算法模板却有很多&#xff0c;这一切都源于二分查找问题中的复杂情况和二分算法的边界处理&#xff0c;以下是博主对一些二分算法查找的情况分析。 需要说明的是&#xff0c;以下二分算法都是基于有序序列为升序有序的情况&#xf…...

selenium学习实战【Python爬虫】

selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...

OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 在 GPU 上对图像执行 均值漂移滤波&#xff08;Mean Shift Filtering&#xff09;&#xff0c;用于图像分割或平滑处理。 该函数将输入图像中的…...

AI,如何重构理解、匹配与决策?

AI 时代&#xff0c;我们如何理解消费&#xff1f; 作者&#xff5c;王彬 封面&#xff5c;Unplash 人们通过信息理解世界。 曾几何时&#xff0c;PC 与移动互联网重塑了人们的购物路径&#xff1a;信息变得唾手可得&#xff0c;商品决策变得高度依赖内容。 但 AI 时代的来…...

PAN/FPN

import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...

「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案

在移动互联网营销竞争白热化的当下&#xff0c;推客小程序系统凭借其裂变传播、精准营销等特性&#xff0c;成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径&#xff0c;助力开发者打造具有市场竞争力的营销工具。​ 一、系统核心功能架构&…...

Kafka主题运维全指南:从基础配置到故障处理

#作者&#xff1a;张桐瑞 文章目录 主题日常管理1. 修改主题分区。2. 修改主题级别参数。3. 变更副本数。4. 修改主题限速。5.主题分区迁移。6. 常见主题错误处理常见错误1&#xff1a;主题删除失败。常见错误2&#xff1a;__consumer_offsets占用太多的磁盘。 主题日常管理 …...

ZYNQ学习记录FPGA(一)ZYNQ简介

一、知识准备 1.一些术语,缩写和概念&#xff1a; 1&#xff09;ZYNQ全称&#xff1a;ZYNQ7000 All Pgrammable SoC 2&#xff09;SoC:system on chips(片上系统)&#xff0c;对比集成电路的SoB&#xff08;system on board&#xff09; 3&#xff09;ARM&#xff1a;处理器…...