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

拓扑排序实现循环依赖判断 | 京东云技术团队

本文记录如何通过拓扑排序,实现循环依赖判断

前言

一般提到循环依赖,首先想到的就是Spring框架提供的Bean的循环依赖检测,相关文档可参考:

https://blog.csdn.net/cristianoxm/article/details/113246104

本文方案脱离Spring Bean的管理,通过算法实现的方式,完成对象循环依赖的判断,涉及的知识点包括:邻接矩阵图、拓扑排序、循环依赖。本文会着重讲解技术实现,具体算法原理不再复述

概念释义

1. 什么是邻接矩阵?

这里要总结的邻接矩阵是关于图的邻接矩阵;图的邻接矩阵(Adjacency Matrix)存储方式是用两个数组来表示图;一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息;

图分为有向图和无向图,其对应的邻接矩阵也不相同,无向图的邻接矩阵是一个对称矩阵,就是一个对称的二位数组,a[i][j] = a[j][i];
邻接矩阵可以清楚的知道图的任意两个顶点是否有边;方便计算任意顶点的度(包括有向图的出度和入度);可以直观的看出任意顶点的邻接点;

本案例中,有向邻接矩阵图为进行拓扑排序的必要条件之一,其次为有向邻接矩阵图每个顶点的入度

2. 邻接矩阵的存储结构?

vexs[MAXVEX]这是顶点表;

arc[MAXVEX][MAXVEX]这是邻接矩阵图,也是存储每条边信息的二维数组。数组的索引是边的两个顶点,数组的数据是边的权值;

numVertexes, numEdges分别为图的顶点数和边数。

3. 有向邻接矩阵图顶点的入度?

在有向图中,箭头是具有方向的,从一个顶点指向另一个顶点,这样一来,每个顶点被指向的箭头个数,就是它的入度。从这个顶点指出去的箭头个数,就是它的出度

邻接矩阵的行号即代表箭头的出发结点,列号是箭头的指向结点,所以矩阵中同一行为1的表示有从第i个结点指向第j个结点这样一条边,而在同列为1就代表第j个结点被第i个结点指向,因此要求顶点的入度或出度,只需要判断同列为1的个数或同行为1的个数

4. 什么是拓扑排序?

拓扑排序的要素:
1.有向无环图;
2.序列里的每一个点只能出现一次;
3.任何一对 u 和 v ,u 总在 v 之前(这里的两个字母分别表示的是一条线段的两个端点,u 表示起点,v 表示终点);

根据拓扑排序的要素,可通过其有向无环来判断对象依赖是否存在循环。若对象组成的图可完成拓扑排序,则该对象图不存在环,即对象间不存在循环依赖。

拓扑排序除了通过有向邻接矩阵图实现外,还可以通过深度优先搜索(DFS)来实现。本案例中仅讲解前者。

5. 什么是循环依赖?

简单解释如下,若存在两个对象,若A创建需要B,B创建需要A,这两个对象间互相依赖,就构成了最简单的循环依赖关系。

编程示例

1. 对象实体

@Builder
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString
public class RelationVo implements Serializable {/*** 唯一标识*/private String uniqueKey;/*** 关联唯一标识集合*/private List combinedUniqueKeys;}

2. 对象集合转换为有向邻接矩阵图

    /*** 将List集合转换为邻接矩阵图的二维数组形式** @param sourceList* @return*/public static int[][] listToAdjacencyMatrixDiagram(List sourceList) {List distinctRelationVoList = new ArrayList(sourceList);List keyCollect = distinctRelationVoList.stream().map(RelationVo::getUniqueKey).collect(Collectors.toList());for (RelationVo vo : sourceList) {vo.getCombinedUniqueKeys().forEach(child -> {if (!keyCollect.contains(child)) {// 若叶子节点不在集合中,补充List集合中单独叶子节点,目的是完成提供邻接矩阵图计算的入参keyCollect.add(child);RelationVo build = RelationVo.builder().uniqueKey(child).build();distinctRelationVoList.add(build);}});}// 顶点数:对象中出现的全部元素总数int vertexNum = keyCollect.size();/** 初始化邻接矩阵图的边的二维数组,1表示有边 0表示无边 权重均为1* 其中数组下标为边的两个顶点,数组值为对象边的权值(权值=是否有边*权重)*/int[][] edges = new int[vertexNum][vertexNum];// 计算邻接矩阵图for (int i = 0; i < vertexNum; i++) {RelationVo colVo = distinctRelationVoList.get(i);List colUniqueKeys = colVo.getCombinedUniqueKeys();for (int j = 0; j < vertexNum; j++) {RelationVo rowVo = distinctRelationVoList.get(j);String rowVertex = rowVo.getUniqueKey();if (CollUtil.isNotEmpty(colUniqueKeys)) {if (colUniqueKeys.contains(rowVertex)) {edges[i][j] = 1;} else {edges[i][j] = 0;}}}}return edges;}

3. 计算邻接矩阵图顶点的入度

     /*** 返回给出图每个顶点的入度值** @param adjMatrix 给出图的邻接矩阵值* @return*/public static int[] getSource(int[][] adjMatrix) {int len = adjMatrix[0].length;int[] source = new int[len];for (int i = 0; i < len; i++) {// 若邻接矩阵中第i列含有m个1,则在该列的节点就包含m个入度,即source[i] = mint count = 0;for (int j = 0; j < len; j++) {if (adjMatrix[j][i] == 1) {count++;}}source[i] = count;}return source;}

4. 对邻接矩阵图进行拓扑排序

    /*** 拓扑排序,返回给出图的拓扑排序序列* 拓扑排序基本思想:* 方法1:基于减治法:寻找图中入度为0的顶点作为即将遍历的顶点,遍历完后,将此顶点从图中删除* 若结果集长度等于图的顶点数,说明无环;若小于图的顶点数,说明存在环** @param adjMatrix 给出图的邻接矩阵值* @param source    给出图的每个顶点的入度值* @return*/public static List topologySort(int[][] adjMatrix, int[] source) {// 给出图的顶点个数int len = source.length;// 定义最终返回路径字符数组List result = new ArrayList(len);// 获取入度为0的顶点下标int vertexFound = findInDegreeZero(source);while (vertexFound != -1) {result.add(vertexFound);// 代表第i个顶点已被遍历source[vertexFound] = -1;for (int j = 0; j < adjMatrix[0].length; j++) {if (adjMatrix[vertexFound][j] == 1) {// 第j个顶点的入度减1source[j] -= 1;}}vertexFound = findInDegreeZero(source);}//输出拓扑排序的结果return result;}/*** 找到入度为0的点,如果存在入度为0的点,则返回这个点;如果不存在,则返回-1** @param source 给出图的每个顶点的入度值* @return*/public static int findInDegreeZero(int[] source) {for (int i = 0; i < source.length; i++) {if (source[i] == 0) {return i;}}return -1;}

5. 检查集合是否存在循环依赖

    /*** 检查集合是否存在循环依赖** @param itemList*/public static void checkCircularDependency(List itemList) throws Exception {if (CollUtil.isEmpty(itemList)) {return;}// 计算邻接矩阵图的二维数组int[][] edges = listToAdjacencyMatrixDiagram(itemList);// 计算邻接矩阵图每个顶点的入度值int[] source = getSource(edges);// 拓扑排序得到拓扑序列List topologySort = topologySort(edges, source);if (source.length == topologySort.size()) {// 无循环依赖return;} else {// 序列集合与顶点集合大小不一致,存在循环依赖throw new Exception("当前险种关系信息存在循环依赖,请检查");}}

单测用例

1. 测试物料-无循环依赖

示例JSON Array结构(可完成拓扑排序):
[{"uniqueKey":"A","combinedUniqueKeys":["C","D","E"]
},
{"uniqueKey":"B","combinedUniqueKeys":["D","E"]
},
{"uniqueKey":"D","combinedUniqueKeys":["C"]
}
]

2. 测试物料-存在循环依赖

示例JSON Array结构(不可完成拓扑排序):
[{"uniqueKey":"A","combinedUniqueKeys":["C","D","E"]
},
{"uniqueKey":"B","combinedUniqueKeys":["D","E"]
},
{"uniqueKey":"D","combinedUniqueKeys":["C"]
},
{"uniqueKey":"C","combinedUniqueKeys":["B"]
}
]

3. 单测示例

@Slf4j
public class CircularDependencyTest {/*** 针对集合信息判断该集合是否存在循环依赖*/@Testvoid testCircularDependencyList() throws Exception {String paramInfo = "[{\"uniqueKey\":\"A\",\"combinedUniqueKeys\":[\"C\",\"D\",\"E\"]},{\"uniqueKey\":\"B\",\"combinedUniqueKeys\":[\"D\",\"E\"]},{\"uniqueKey\":\"D\",\"combinedUniqueKeys\":[\"C\"]}]";// 序列化List list = JSONArray.parseArray(paramInfo, RelationVo.class);TopologicalSortingUtil.checkCircularDependency(list);}
}

作者:京东保险 侯亚东

来源:京东云开发者社区 转载请注明来源

相关文章:

拓扑排序实现循环依赖判断 | 京东云技术团队

本文记录如何通过拓扑排序&#xff0c;实现循环依赖判断 前言 一般提到循环依赖&#xff0c;首先想到的就是Spring框架提供的Bean的循环依赖检测&#xff0c;相关文档可参考&#xff1a; https://blog.csdn.net/cristianoxm/article/details/113246104 本文方案脱离Spring Be…...

Java的NIO工作机制

文章目录 1. 问题引入2. NIO的工作方式3. Buffer的工作方式4. NIO数据访问方式 1. 问题引入 在网络通信中&#xff0c;当连接已经建立成功&#xff0c;服务端和客户端都会拥有一个Socket实例&#xff0c;每个Socket实例都有一个InputStream和OutputStream&#xff0c;并通过这…...

一个简单的光线追踪渲染器

前言 本文参照自raytracing in one weekend教程&#xff0c;地址为&#xff1a;https://raytracing.github.io/books/RayTracingInOneWeekend.html 什么是光线追踪&#xff1f; 光线追踪模拟现实中的成像原理&#xff0c;通过模拟一条条直线在场景内反射折射&#xff0c;最终…...

C++学习笔记(十二)------is_a关系(继承关系)

你好&#xff0c;这里是争做图书馆扫地僧的小白。 个人主页&#xff1a;争做图书馆扫地僧的小白_-CSDN博客 目标&#xff1a;希望通过学习技术&#xff0c;期待着改变世界。 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 文章目录 前言 一、继承关系…...

DC电源模块的设计与制造技术创新

BOSHIDA DC电源模块的设计与制造技术创新 DC电源模块的设计与制造技术创新主要涉及以下几个方面&#xff1a; 1. 高效率设计&#xff1a;传统的DC电源模块存在能量转换损耗较大的问题&#xff0c;技术创新可通过采用高效率的电路拓扑结构、使用高性能的功率开关器件和优化控制…...

Sketch for Mac:实现你的创意绘图梦想的矢量绘图软件

随着数字时代的到来&#xff0c;矢量绘图软件成为了广告设计、插画创作和UI设计等领域中必不可少的工具。在众多矢量绘图软件中&#xff0c;Sketch for Mac&#xff08;矢量绘图软件&#xff09;以其强大的功能和简洁的界面脱颖而出&#xff0c;成为了众多设计师的首选。 Sket…...

ReactNative0.73发布,架构升级与更好的调试体验

这次更新包含了多种提升开发体验的改进&#xff0c;包括&#xff1a; 更流畅的调试体验: 通过 Hermes 引擎调试支持、控制台日志历史记录和实验性调试器&#xff0c;让调试过程更加高效顺畅。稳定的符号链接支持: 简化您的开发工作流程&#xff0c;轻松将文件或目录链接到其他…...

SVN忽略文件的两种方式

当使用版本管理工具时&#xff0c;提交到代码库的文档我们不希望存在把一些临时文件也推送到仓库中&#xff0c;这样就需要用到忽略文件。SVN的忽略相比于GIT稍显麻烦&#xff0c;GIT只需要在.gitignore添加忽略规则即可。而SVN有两种忽略方式&#xff0c;一个是全局设置&#…...

手写VUE后台管理系统10 - 封装Axios实现异常统一处理

目录 前后端交互约定安装创建Axios实例拦截器封装请求方法业务异常处理 axios 是一个易用、简洁且高效的http库 axios 中文文档&#xff1a;http://www.axios-js.com/zh-cn/docs/ 前后端交互约定 在本项目中&#xff0c;前后端交互统一使用 application/json;charsetUTF-8 的请…...

JavaScript装饰者模式

JavaScript装饰者模式 1 什么是装饰者模式2 模拟装饰者模式3 JavaScript的装饰者4 装饰函数5 AOP装饰函数6 示例&#xff1a;数据统计上报 1 什么是装饰者模式 在程序开发中&#xff0c;许多时候都我们并不希望某个类天生就非常庞大&#xff0c;一次性包含许多职责。那么我们就…...

C++学习笔记01

01.C概述&#xff08;了解&#xff09; c语言在c语言的基础上添加了面向对象编程和泛型编程的支持。 02.第一个程序helloworld&#xff08;掌握&#xff09; #define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std;//标准命名空间int main() {//co…...

【UE5】初识MetaHuman 创建虚拟角色

步骤 在UE5工程中启用“Quixel Bridge”插件 打开“Quixel Bridge” 点击“MetaHumans-》MetaHuman Presets UE5” 点击“START MHC” 在弹出的网页中选择一个虚幻引擎版本&#xff0c;然后点击“启动 MetaHuman Creator” 等待一段时间后&#xff0c;在如下页面点击选择一个人…...

物流实时数仓:数仓搭建(DWD)一

系列文章目录 物流实时数仓&#xff1a;采集通道搭建 物流实时数仓&#xff1a;数仓搭建 物流实时数仓&#xff1a;数仓搭建&#xff08;DIM&#xff09; 物流实时数仓&#xff1a;数仓搭建&#xff08;DWD&#xff09;一 文章目录 系列文章目录前言一、文件编写1.目录创建2.b…...

MATLAB安装

亲自验证有效&#xff0c;多谢这位网友的分享&#xff1a; https://blog.csdn.net/xiajinbiaolove/article/details/88907232...

C语言——预处理详解(#define用法+注意事项)

#define 语法规定 #define定义标识符 语法: #define name stuff #define例子 #include<stdio.h> #define A 100 #define STR "abc" #define FOR for(;;)int main() {printf("%d\n", A);printf("%s\n", STR);FOR;return 0; } 运行结果…...

Linux(23):Linux 核心编译与管理

编译前的任务&#xff1a;认识核心与取得核心原始码 Linux 其实指的是核心。这个【核心(kernel)】是整个操作系统的最底层&#xff0c;他负责了整个硬件的驱动&#xff0c;以及提供各种系统所需的核心功能&#xff0c;包括防火墙机制、是否支持 LVM 或 Quota 等文件系统等等&a…...

Oracle RAC环境下redo log 文件的扩容

环境&#xff1a; 有一个2节点RAC每一个节点2个logfile group每一个group含2个member每一个member的大小为200M 目标&#xff1a;将每一个member的大小有200M扩充到1G。 先来看下redo log的配置&#xff1a; SQL> select * from v$log;GROUP# THREAD# SEQUENCE# …...

Java入门学习笔记一

一、Java语言环境搭建 1、JAVA语言的跨平台原理 1.1、什么是跨平台性&#xff1f; 跨平台就是说&#xff0c;同一个软件可以在不同的操作系统&#xff08;例如&#xff1a;Windows、Linux、mad&#xff09;上执行&#xff0c;而不需要对软件做任务处理。即通过Java语言编写的…...

分布式块存储 ZBS 的自主研发之旅|元数据管理

重点内容 元数据管理十分重要&#xff0c;犹如整个存储系统的“大黄页”&#xff0c;如果元数据操作出现性能瓶颈&#xff0c;将严重影响存储系统的整体性能。如何提升元数据处理速度与高可用是元数据管理的挑战之一。SmartX 分布式存储 ZBS 采用 Log Replication 的机制&…...

六大设计原则

六大设计原则 1、单一职责原则 一个类或者模块只负责完成一个职责或者功能。 2、开放封闭原则 规定软件中的对象、类、模块和函数对扩展应该是开放的&#xff0c;对于修改应该是封闭的。用抽象定义结构&#xff0c;用具体实现扩展细节。 3、里氏替换原则 如果S是T的子类型…...

【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表

1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

零基础设计模式——行为型模式 - 责任链模式

第四部分&#xff1a;行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习&#xff01;行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想&#xff1a;使多个对象都有机会处…...

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 提…...

#Uniapp篇:chrome调试unapp适配

chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器&#xff1a;Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...

HDFS分布式存储 zookeeper

hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架&#xff0c;允许使用简单的变成模型跨计算机对大型集群进行分布式处理&#xff08;1.海量的数据存储 2.海量数据的计算&#xff09;Hadoop核心组件 hdfs&#xff08;分布式文件存储系统&#xff09;&a…...

莫兰迪高级灰总结计划简约商务通用PPT模版

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

Python Einops库:深度学习中的张量操作革命

Einops&#xff08;爱因斯坦操作库&#xff09;就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库&#xff0c;用类似自然语言的表达式替代了晦涩的API调用&#xff0c;彻底改变了深度学习工程…...

Spring AI Chat Memory 实战指南:Local 与 JDBC 存储集成

一个面向 Java 开发者的 Sring-Ai 示例工程项目&#xff0c;该项目是一个 Spring AI 快速入门的样例工程项目&#xff0c;旨在通过一些小的案例展示 Spring AI 框架的核心功能和使用方法。 项目采用模块化设计&#xff0c;每个模块都专注于特定的功能领域&#xff0c;便于学习和…...

用 Rust 重写 Linux 内核模块实战:迈向安全内核的新篇章

用 Rust 重写 Linux 内核模块实战&#xff1a;迈向安全内核的新篇章 ​​摘要&#xff1a;​​ 操作系统内核的安全性、稳定性至关重要。传统 Linux 内核模块开发长期依赖于 C 语言&#xff0c;受限于 C 语言本身的内存安全和并发安全问题&#xff0c;开发复杂模块极易引入难以…...

JavaScript 标签加载

目录 JavaScript 标签加载script 标签的 async 和 defer 属性&#xff0c;分别代表什么&#xff0c;有什么区别1. 普通 script 标签2. async 属性3. defer 属性4. type"module"5. 各种加载方式的对比6. 使用建议 JavaScript 标签加载 script 标签的 async 和 defer …...