多叉树+图实现简单业务流程
文章目录
- 场景
- 整体架构流程
- 业务界面
- 技术细节
- 小结
场景
这次遇到一个需求,大致就是任务组织成方案,方案组织成预案,预案可裁剪调整.预案关联事件等级配置,告警触发预案产生事件.然后任务执行是有先后的,也就是有流程概念.
整体架构流程
方案管理、预案管理构成任务流程的基础条件,告警信息关联预案配置构成事件,也就是流程启动的入口信息.
业务界面
技术细节
其实也没有什么特殊的技术,也就用到了多叉树、图、最长路径计算(深搜)等.
- 多叉树
import lombok.Data;import java.util.ArrayList;
import java.util.List;/*** @author zwmac*/
@Data
public class MoreParentTreeNode<T> {/*** 节点的唯一标识符*/private Long id;/*** 节点的名称*/private String name;/*** 节点的索引*/private Integer index;/*** 子节点list*/private List<MoreParentTreeNode<T>> children;/*** 父节点list*/private List<MoreParentTreeNode<T>> parents;/*** 扩展信息(一般就存节点对应的原始数据)*/private T extendInfo;public MoreParentTreeNode(Long id, String name,Integer index) {this.id = id;this.name = name;this.index = index;this.children = new ArrayList<>();this.parents = new ArrayList<>();}public MoreParentTreeNode() {}/*** 添加子节点* @param child 子节点*/public void addChild(MoreParentTreeNode<T> child) {if (this.children.contains(child)) {return;}this.children.add(child);child.parents.add(this);}/*** 添加父节点* @param parent 父节点*/public void addParent(MoreParentTreeNode<T> parent) {if (this.parents.contains(parent)) {return;}this.parents.add(parent);parent.children.add(this);}public void traverse() {System.out.println(this.name); // 打印节点名称if (this.children.isEmpty()) {System.out.println("没有子节点");}for (MoreParentTreeNode<T> child : this.children) {child.traverse(); // 递归遍历子节点}}public static void main(String[] args) {MoreParentTreeNode root = new MoreParentTreeNode(1L, "root",1);MoreParentTreeNode node1 = new MoreParentTreeNode(2L, "node1",2);MoreParentTreeNode node2 = new MoreParentTreeNode(3L, "node2",3);MoreParentTreeNode node3 = new MoreParentTreeNode(4L, "node3",4);MoreParentTreeNode node4 = new MoreParentTreeNode(5L, "node4",5);MoreParentTreeNode node5 = new MoreParentTreeNode(5L, "node5",6);MoreParentTreeNode node6 = new MoreParentTreeNode(6L, "node6",7);root.addChild(node1);root.addChild(node2);node1.addChild(node6);node3.addParent(node2);node4.addParent(node2);node4.addParent(node1);node5.addParent(node4);root.traverse();}
}
- 图对象与计算基本方法
import lombok.Data;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;/*** 流程图对象,用于计算最长执行时间* @author zwmac*/
@Data
public class ProcessGraph {/*** 顶点个数*/private int numVertices;/*** 邻接表*/private List<List<Integer>> adjList;/*** 顶点执行时间(边长)*/private Double[] executionTimes;/*** 构造函数* @param numVertices 顶点个数* @param executionTimes 顶点执行时间(边长)*/public ProcessGraph(int numVertices, Double[] executionTimes) {this.numVertices = numVertices;this.executionTimes = executionTimes;adjList = new ArrayList<>(numVertices);for (int i = 0; i < numVertices; i++) {adjList.add(new ArrayList<>());}}/*** 添加边* @param src 源顶点* @param dest 目标顶点*/public void addEdge(int src, int dest) {adjList.get(src).add(dest);}/*** 计算最长执行时间* @return 最长执行时间*/public Double findMaxExecutionTime() {Double[] maxExecutionTimes = new Double[numVertices];Arrays.fill(maxExecutionTimes, -1d);Double maxExecutionTime = 0d;for (int i = 0; i < numVertices; i++) {Double currentExecutionTime = dfs(i, maxExecutionTimes);if (currentExecutionTime > maxExecutionTime) {maxExecutionTime = currentExecutionTime;}}return maxExecutionTime;}/*** 深度优先搜索* @param vertex 顶点* @param maxExecutionTimes 最长执行时间数组* @return 最长执行时间*/private Double dfs(int vertex, Double[] maxExecutionTimes) {if (maxExecutionTimes[vertex] != -1) {return maxExecutionTimes[vertex];}Double maxExecutionTime = executionTimes[vertex];for (int neighbor : adjList.get(vertex)) {Double neighborExecutionTime = dfs(neighbor, maxExecutionTimes);if (neighborExecutionTime + executionTimes[vertex] > maxExecutionTime) {maxExecutionTime = neighborExecutionTime + executionTimes[vertex];}}maxExecutionTimes[vertex] = maxExecutionTime;return maxExecutionTime;}public static void main(String[] args) {Double[] executionTimes = {0d,3d, 4d, 5d, 2d, 3d,0d};ProcessGraph graph = new ProcessGraph(7, executionTimes);graph.addEdge(0, 1);graph.addEdge(0, 2);graph.addEdge(1, 3);graph.addEdge(1, 4);graph.addEdge(2, 3);graph.addEdge(3, 5);graph.addEdge(3, 5);//graph.addEdge(3, 5);Double maxExecutionTime = graph.findMaxExecutionTime();System.out.println("Max execution time: " + maxExecutionTime);}
}
- 多叉树工具类
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONUtil;
import com.smartPark.business.emergency.event.entity.EmergencyEventTask;
import com.smartPark.business.emergency.event.entity.dto.EmergencyEventTaskDTO;
import org.apache.commons.lang3.StringUtils;import java.util.*;/*** @author zwmac*/
public class EventTaskTreeUtil {/*** 转换流程树* @param eventTaskList 任务列表* @return 流程树根节点*/public static MoreParentTreeNode<?> convertTreeDto(List<EmergencyEventTaskDTO> eventTaskList) {MoreParentTreeNode<?> root = new MoreParentTreeNode<>(0L,"开始",0);EmergencyEventTaskDTO rootEventTaskDto = new EmergencyEventTaskDTO();rootEventTaskDto.setTaskDuration(0d);Map<Long, MoreParentTreeNode<?>> nodeMap = new HashMap<>();for (int i = 0;i < eventTaskList.size();i++) {EmergencyEventTaskDTO eventTask = eventTaskList.get(i);Long id = eventTask.getId();String name = eventTask.getTaskName();MoreParentTreeNode<EmergencyEventTaskDTO> node = new MoreParentTreeNode<>(id,name,(i+1));node.setExtendInfo(eventTask);nodeMap.put(eventTask.getId(), node);}//处理父子关系(正)for (EmergencyEventTaskDTO eventTask : eventTaskList) {transNodeRef(eventTask, nodeMap, root);}//处理父子关系(反)for (int i = eventTaskList.size() - 1;i > -1; i--){EmergencyEventTaskDTO eventTask = eventTaskList.get(i);transNodeRef(eventTask, nodeMap, root);}return root;}/*** 转换流程树* @param eventTaskList 任务列表* @return 流程树根节点*/public static MoreParentTreeNode<?> convertTree(List<EmergencyEventTask> eventTaskList) {MoreParentTreeNode<?> root = new MoreParentTreeNode<>(0L,"开始",0);EmergencyEventTask rootEventTask = new EmergencyEventTask();rootEventTask.setTaskDuration(0d);Map<Long, MoreParentTreeNode<?>> nodeMap = new HashMap<>();for (int i = 0;i < eventTaskList.size();i++) {EmergencyEventTask eventTask = eventTaskList.get(i);Long id = eventTask.getId();String name = eventTask.getTaskName();MoreParentTreeNode<EmergencyEventTask> node = new MoreParentTreeNode<>(id,name,(i+1));node.setExtendInfo(eventTask);nodeMap.put(eventTask.getId(), node);}//处理父子关系(正)for (EmergencyEventTask eventTask : eventTaskList) {EmergencyEventTaskDTO eventTaskDTO = new EmergencyEventTaskDTO();BeanUtil.copyProperties(eventTask, eventTaskDTO);transNodeRef(eventTaskDTO, nodeMap, root);}//处理父子关系(反)for (int i = eventTaskList.size() - 1;i > -1; i--){EmergencyEventTask eventTask = eventTaskList.get(i);EmergencyEventTaskDTO eventTaskDTO = new EmergencyEventTaskDTO();BeanUtil.copyProperties(eventTask,eventTaskDTO);transNodeRef(eventTaskDTO, nodeMap, root);}return root;}/*** 转换节点关系* @param eventTask 父级任务* @param nodeMap 节点map* @param root 根节点*/private static void transNodeRef(EmergencyEventTaskDTO eventTask, Map<Long, MoreParentTreeNode<?>> nodeMap, MoreParentTreeNode<?> root) {Long id = eventTask.getId();MoreParentTreeNode<?> node = nodeMap.get(id);if (node != null){EmergencyEventTaskDTO eventTaskDTO = new EmergencyEventTaskDTO();Object eventTaskObj = node.getExtendInfo();if(eventTaskObj instanceof EmergencyEventTask) {EmergencyEventTask nodeEventTask = (EmergencyEventTask) eventTaskObj;BeanUtil.copyProperties(nodeEventTask, eventTaskDTO);}String preIds = eventTaskDTO.getBeforeTaskIds();if (StringUtils.isEmpty(preIds)){node.addParent(root);root.addChild(node);}else {String[] preIdsArr = preIds.split(",");if (ArrayUtil.isNotEmpty(preIdsArr)){for (String preId : preIdsArr) {MoreParentTreeNode<?> preNode = nodeMap.get(Long.valueOf(preId));//前置任务不为空if (preNode != null){preNode.addChild(node);}}}}nodeMap.put(id, node);}}/*** 获取节点的子节点列表* @param treeNode 节点* @param eventTaskId 事件任务id* @return 子节点列表*/public static List<? extends MoreParentTreeNode<?>> getNodeChildrenList(MoreParentTreeNode<?> treeNode, Long eventTaskId) {List<? extends MoreParentTreeNode<?>> childrenList = itGetNodeChildrenList(treeNode, eventTaskId);if (CollectionUtil.isNotEmpty(childrenList)) {return childrenList;}childrenList = itGetNodeParentList(treeNode, eventTaskId);return childrenList;}/*** 迭代获取节点的父节点列表* @param moreParentTreeNode 父节点* @param eventTaskId 事件任务id* @return 父节点列表*/private static List<? extends MoreParentTreeNode<?>> itGetNodeParentList(MoreParentTreeNode<?> moreParentTreeNode, Long eventTaskId) {List<? extends MoreParentTreeNode<?>> children = null;if (moreParentTreeNode.getId().equals(eventTaskId)){children = moreParentTreeNode.getChildren();}else{List<? extends MoreParentTreeNode<?>> parentTreeNodes = moreParentTreeNode.getParents();if(CollectionUtil.isNotEmpty(parentTreeNodes)) {for (MoreParentTreeNode<?> parentTreeNode : parentTreeNodes) {children = itGetNodeParentList(parentTreeNode, eventTaskId);if (CollectionUtil.isNotEmpty(children)) {break;}}}}return children;}/*** 迭代获取节点的子节点列表* @param moreParentTreeNode 子节点* @param eventTaskId 事件任务id* @return 子节点列表*/private static List<? extends MoreParentTreeNode<?>> itGetNodeChildrenList(MoreParentTreeNode<?> moreParentTreeNode, Long eventTaskId) {List<? extends MoreParentTreeNode<?>> children = null;if (moreParentTreeNode.getId().equals(eventTaskId)){children = moreParentTreeNode.getChildren();}else{List<? extends MoreParentTreeNode<?>> childrenChild = moreParentTreeNode.getChildren();if(CollectionUtil.isNotEmpty(childrenChild)) {for (MoreParentTreeNode<?> parentTreeNode : childrenChild) {children = itGetNodeChildrenList(parentTreeNode, eventTaskId);if (CollectionUtil.isNotEmpty(children)) {break;}}}}return children;}public static void main(String[] args) {List<EmergencyEventTaskDTO> eventTaskList = new ArrayList<>();initTestData(eventTaskList,9);System.out.println(JSONUtil.toJsonStr(eventTaskList));MoreParentTreeNode<?> treeNode = convertTreeDto(eventTaskList);List<?> childrenList = getNodeChildrenList(treeNode, 3L);treeNode.traverse();}private static void initTestData(List<EmergencyEventTaskDTO> eventTaskList, int num) {for (int i = 0;i < num; i++){EmergencyEventTaskDTO eventTask = new EmergencyEventTaskDTO();eventTask.setId(Long.valueOf(i));eventTask.setTaskName("任务-" + (i + 1));eventTask.setTaskDuration(RandomUtil.randomDouble(1,10));if (i > 1){StringJoiner sj = new StringJoiner(",");int n = RandomUtil.randomInt(0,i);for(int j = 0;j < i - 1 - n;j++){sj.add(String.valueOf(j+1));}eventTask.setBeforeTaskIds(sj.toString());}eventTaskList.add(eventTask);}}}
使用思路也特别简单,实际就是将配置任务时只选择了节点上级任务的所有任务构成一个多叉树,然后跟这个树每个节点设置唯一编号,然后转化为图,计算最长路径作为事件的预计完成时间,最后就是各个任务节点的流转了.
/*** 计算预计完成时间* @param event 事件* @return 预计完成时间*/private Date calPlanEndTime(EmergencyEvent event) {Date planStartTime = event.getPlanStartTime();Date finalNow = planStartTime;//先查询预案任务QueryWrapper<EmergencyEventPlan> eventPlanQw = new QueryWrapper<>();eventPlanQw.eq("event_id_", event.getId());List<EmergencyEventPlan> eventPlanList = eventPlanService.list(eventPlanQw);if (CollectionUtil.isNotEmpty(eventPlanList)){//最终预计完成时间//再查询预案任务for (int i = 0;i < eventPlanList.size();i++){QueryWrapper<EmergencyEventTask> eventTaskQw = new QueryWrapper<>();eventTaskQw.eq("event_id_", event.getId());eventTaskQw.eq("event_plan_id_", eventPlanList.get(i).getId());List<EmergencyEventTask> eventTaskList = eventPlanTaskService.list(eventTaskQw);if (CollectionUtil.isNotEmpty(eventTaskList)){//再组织流程树MoreParentTreeNode<?> processTree = EventTaskTreeUtil.convertTree(eventTaskList);//最后计算时间Date planTaskEndTime = calEndTime(processTree, planStartTime, eventTaskList);if (planTaskEndTime.after(finalNow)){finalNow = planTaskEndTime;}}}}return finalNow;}
小结
- 后悔数据结构没有学好
- 算法用的少,也有很大工具包直接提供,但是还是很有学的必要的
就随便写写,希望能帮到大家,uping!
相关文章:

多叉树+图实现简单业务流程
文章目录 场景整体架构流程业务界面技术细节小结 场景 这次遇到一个需求,大致就是任务组织成方案,方案组织成预案,预案可裁剪调整.预案关联事件等级配置,告警触发预案产生事件.然后任务执行是有先后的,也就是有流程概念. 整体架构流程 方案管理、预案管理构成任务流程的基础条…...

Word | 简单可操作的快捷公式编号、右对齐和引用方法
1. 问题描述 在理工科论文的写作中,涉及到大量的公式输入,我们希望能够按照章节为公式进行编号,并且实现公式居中,编号右对齐的效果。网上有各种各样的方法来实现,操作繁琐和简单的混在一起,让没有接触过公…...

leetCode 123.买卖股票的最佳时机 III 动态规划 + 状态压缩
123. 买卖股票的最佳时机 III - 力扣(LeetCode) 给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。 注意:你不能同时参与多笔交易࿰…...
JavaScript计算两个时间相差多少个小时的封装函数
js中计算两个时间相差小时数 在JavaScript中,你可以使用Date对象来处理日期和时间。下面是一个函数,它接受两个时间字符串作为参数,并返回两者之间的时间差(以小时为单位): function calculateHours(time…...

Qt 画自定义饼图统计的例子
先给出结果图,这个例子是将各种事件分类然后统计的其比例,然后画饼图显示出来 这个是我仿照官方给的例子,让后自己理解后,修改的,要生成饼图,需要QT的 charts 支持,安装QT 没有选择这个的&#…...

【数据结构】链表与LinkedList
作者主页:paper jie 的博客 本文作者:大家好,我是paper jie,感谢你阅读本文,欢迎一建三连哦。 本文录入于《JAVA数据结构》专栏,本专栏是针对于大学生,编程小白精心打造的。笔者用重金(时间和精…...
Flink RoaringBitmap去重
1、RoaringBitmap的依赖 <!-- 去重大哥--> <dependency><groupId>org.roaringbitmap</groupId><artifactId>RoaringBitmap</artifactId><version>0.9.21</version> </dependency> 2、Demo去重 package com.gwm.driver…...

Elasticsearch—(MacOs)
1⃣️环境准备 准备 Java 环境:终端输入 java -version 命令来确认版本是否符合 Elasticsearch 要求下载并解压 Elasticsearch:前往(https://www.elastic.co/downloads/elasticsearch)选择适合你的 Mac 系统的 Elasticsearch 版本…...

插入排序与希尔排序
个人主页:Lei宝啊 愿所有美好如期而遇 前言: 这两个排序在思路上有些相似,所以有人觉得插入排序和希尔排序差别不大,事实上,他们之间的差别不小,插入排序只是希尔排序的最后一步。 目录 前言:…...

C# OpenCvSharp 基于直线检测的文本图像倾斜校正
效果 项目 代码 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using OpenCvSharp;namespace OpenCvSharp_基于直线检测的文…...

“智慧时代的引领者:探索人工智能的无限可能性“
目录 一.背景 二.应用 2.1金融领域 2.2医疗领域 2.3教育领域 三.发展 四.总结: 一.背景 人工智能(Artificial Intelligence,简称AI),是指通过计算机程序模拟人类智能的一种技术。它是计算机科学、工程学、语言学、哲学等多…...

PMSM——转子位置估算基于QPLL
文章目录 前言仿真模型观测器速度观测位置观测转矩波形电流波形 前言 今后是电机控制方向的研究生的啦,期待有同行互相交流。 仿真模型 观测器 速度观测 位置观测 转矩波形 电流波形...
Android Studio之Gradle和Gradle插件的区别
解释的很详细 Android Studio之Gradle和Gradle插件的区别...

DataExcel控件读取和保存excel xlsx 格式文件
需要引用NPOI库 https://github.com/dotnetcore/NPOI 调用Read 函数将excel读取到dataexcel控件 调用Save 函数将dataexcel控件文件保存为excel文件 using NPOI.HSSF.UserModel; using NPOI.HSSF.Util; using NPOI.SS.UserModel; using NPOI.SS.Util; using System; using …...

【JavaEE】CAS(Compare And Swap)操作
文章目录 什么是 CASCAS 的应用如何使用 CAS 操作实现自旋锁CAS 的 ABA 问题CAS 相关面试题 什么是 CAS CAS(Compare and Swap)是一种原子操作,用于在无锁情况下保证数据一致性的问题。它包含三个操作数——内存位置、预期原值及更新值。在执…...
第三章:最新版零基础学习 PYTHON 教程(第三节 - Python 运算符—Python 中的关系运算符)
关系运算符用于比较值。它根据条件返回 True 或 False。这些运算符也称为比较运算符。 操作员描述 句法> 大于:如果左操作数大于右操作数,则为 Truex > y...

【GDB】使用 GDB 自动画红黑树
阅读本文前需要的基础知识 用 python 扩展 gdb python 绘制 graphviz 使用 GDB 画红黑树 前面几节中介绍了 gdb 的 python 扩展,参考 用 python 扩展 gdb 并且 python 有 graphviz 模块,那么可以用 gdb 调用 python,在 python 中使用 grap…...

使用Vue3+elementPlus的Tree组件实现一个拖拽文件夹管理
文章目录 1、前言2、分析3、实现4、踩坑4.1、拖拽辅助线的坑4.2、数据的坑4.3、限制拖拽4.4、样式调整 1、前言 最近在做一个文件夹管理的功能,要实现一个树状的文件夹面板。里面包含两种元素,文件夹以及文件。交互要求如下: 创建、删除&am…...

小谈设计模式(7)—装饰模式
小谈设计模式(7)—装饰模式 专栏介绍专栏地址专栏介绍 装饰模式装饰模式角色Component(抽象组件)ConcreteComponent(具体组件)Decorator(抽象装饰器)ConcreteDecorator(具…...

nginx 多层代理 + k8s ingress 后端服务获取客户真实ip 配置
1.nginx http 七层代理 修改命令空间: namespace: nginx-ingress : configmap:nginx-configuration kubectl get cm nginx-configuration -n ingress-nginx -o yaml添加如上配置 compute-full-forwarded-for: “true” forwarded-for-header: X-Forwa…...

国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...

【Oracle APEX开发小技巧12】
有如下需求: 有一个问题反馈页面,要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据,方便管理员及时处理反馈。 我的方法:直接将逻辑写在SQL中,这样可以直接在页面展示 完整代码: SELECTSF.FE…...

【JavaEE】-- HTTP
1. HTTP是什么? HTTP(全称为"超文本传输协议")是一种应用非常广泛的应用层协议,HTTP是基于TCP协议的一种应用层协议。 应用层协议:是计算机网络协议栈中最高层的协议,它定义了运行在不同主机上…...
QMC5883L的驱动
简介 本篇文章的代码已经上传到了github上面,开源代码 作为一个电子罗盘模块,我们可以通过I2C从中获取偏航角yaw,相对于六轴陀螺仪的yaw,qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...

UDP(Echoserver)
网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法:netstat [选项] 功能:查看网络状态 常用选项: n 拒绝显示别名&#…...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...

Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...
蓝桥杯 冶炼金属
原题目链接 🔧 冶炼金属转换率推测题解 📜 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V,是一个正整数,表示每 V V V 个普通金属 O O O 可以冶炼出 …...