多叉树+图实现简单业务流程
文章目录
- 场景
- 整体架构流程
- 业务界面
- 技术细节
- 小结
场景
这次遇到一个需求,大致就是任务组织成方案,方案组织成预案,预案可裁剪调整.预案关联事件等级配置,告警触发预案产生事件.然后任务执行是有先后的,也就是有流程概念.
整体架构流程
方案管理、预案管理构成任务流程的基础条件,告警信息关联预案配置构成事件,也就是流程启动的入口信息.
业务界面
技术细节
其实也没有什么特殊的技术,也就用到了多叉树、图、最长路径计算(深搜)等.
- 多叉树
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…...

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?
编辑:陈萍萍的公主一点人工一点智能 未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战,在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...

使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...

大型活动交通拥堵治理的视觉算法应用
大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动(如演唱会、马拉松赛事、高考中考等)期间,城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例,暖城商圈曾因观众集中离场导致周边…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...

React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...

C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...

使用LangGraph和LangSmith构建多智能体人工智能系统
现在,通过组合几个较小的子智能体来创建一个强大的人工智能智能体正成为一种趋势。但这也带来了一些挑战,比如减少幻觉、管理对话流程、在测试期间留意智能体的工作方式、允许人工介入以及评估其性能。你需要进行大量的反复试验。 在这篇博客〔原作者&a…...

Git 3天2K星标:Datawhale 的 Happy-LLM 项目介绍(附教程)
引言 在人工智能飞速发展的今天,大语言模型(Large Language Models, LLMs)已成为技术领域的焦点。从智能写作到代码生成,LLM 的应用场景不断扩展,深刻改变了我们的工作和生活方式。然而,理解这些模型的内部…...