遍历有向网格链路实现

news2024/11/15 17:23:04

在实际的业务中,我们可能遇到复杂规则(多个或与条件组合),复杂链路等类似场景问题,如:规则引擎相关业务,生产任务排期等。

复杂链路示意图如下:
在这里插入图片描述

复杂网路链路场景描述

  1. 有一个或多个开始节点,有一个或多个结束节点;
  2. 各节点通过有向箭头来描述节点之间的关系;
  3. 关系节点之间不可形成回路;
  4. 节点数量不固定,关系不固定。

程序如何算出所有链路?

设计思路:

节点场景:

  • 开始节点:如图编号1所示
  • 中间节点:如图编号2所示
  • 终止节点:如图编号3所示
  • 零节点:没有关系的节点,如图编号4所示

在这里插入图片描述

如何定义数据模型去描述节点之间的关系呢?

@Data
public class LinkItem {

    // 该节点ID
    private Integer id;

    // 可到达该节点的ID列表
    private List<Integer> pre;

    // 该节点可以到达哪些节点的ID列表
    private List<Integer> next;

    public LinkItem(Integer id, List<Integer> pre, List<Integer> next) {
        this.id = id;
        this.pre = pre;
        this.next = next;
    }
}

如何校验回路链路?

如下图形成了回路:
在这里插入图片描述
思路:链路是由一个个节点有序链接而成,出现了回路,就说明遍历到该节点时,该节点或该节点的next节点出现在该链路中了。

关键代码

package com.example.demo.util;

import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson2.JSON;
import com.example.demo.domain.Link;
import com.example.demo.domain.LinkItem;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Slf4j
public class LinkHandlerUtil {

    public static List<Link> getAllLink(List<LinkItem> linkItems) {

        if (CollUtil.isEmpty(linkItems)) {
            log.info("LinkHandlerUtil.getRuleCondition(), linkItems is null");
            throw new RuntimeException("参数为空");
        }

        // 链路数据处理
        List<Link> result = new ArrayList<>();
        handlerLinkData(result, linkItems);

        return result;
    }

    /**
     * 找出所有链路数据
     *
     * @param list
     * @param linkItems
     */
    private static void handlerLinkData(List<Link> list, List<LinkItem> linkItems) {

        // 1.找出初始链路
        for (LinkItem linkItem : linkItems) {
            if (CollUtil.isEmpty(linkItem.getPre())) {
                linkItemHandler(linkItem, list, linkItems);
            }
        }

        // 2. 递归出所有链路
        boolean flag = allLinkIsEnd(list, linkItems);
        while (!flag) {

            for (LinkItem linkItem : linkItems) {
                if (CollUtil.isNotEmpty(linkItem.getPre())) {
                    linkItemHandler(linkItem, list, linkItems);
                }
            }

            flag = allLinkIsEnd(list, linkItems);
        }
    }

    /**
     * 校验该链路是否结束
     *
     * @param link
     * @param linkItems
     * @return
     */
    private static boolean linkIsEnd(Link link, List<LinkItem> linkItems) {

        List<Integer> itemIds = link.getItemIds();
        LinkItem linkItem = getItemById(itemIds.get(itemIds.size() - 1), linkItems);
        if (CollUtil.isNotEmpty(linkItem.getNext())) {
            return false;
        }

        return true;
    }

    /**
     * 判断所有链路是否都结束了
     *
     * @param links
     * @param linkItems
     * @return
     */
    private static boolean allLinkIsEnd(List<Link> links, List<LinkItem> linkItems) {

        if(CollUtil.isEmpty(links)) {
            return true;
        }

        for (Link link : links) {
            boolean flag = linkIsEnd(link, linkItems);
            if (!flag) {
                return false;
            }
        }

        return true;
    }

    /**
     * 获取ItemById
     * @param id
     * @param linkItems
     * @return
     */
    private static LinkItem getItemById(Integer id, List<LinkItem> linkItems) {

        for (LinkItem linkItem : linkItems) {
            if (linkItem.getId().equals(id)) {
                return linkItem;
            }
        }

        return null;
    }

    /**
     * 节点校验
     * @param id
     * @param link
     * @param linkItems
     */
    private static void itemIdIsValid(Integer id, Link link, List<LinkItem> linkItems) {

        LinkItem linkItem = getItemById(id, linkItems);
        if (null == linkItem) {
            throw new RuntimeException("参数linkItem为空");
        }

        // 链路是否包含当前节点校验
        List<Integer> itemIds = link.getItemIds();
        if (itemIds.contains(linkItem.getId())) {
            throw new RuntimeException("参数链路规则校验失败");
        }

        // 链路是否包含当前节点的next节点校验
        if (CollUtil.isNotEmpty(linkItem.getNext())) {
            for (Integer itemId : linkItem.getNext()) {
                if (itemIds.contains(itemId)) {
                    throw new RuntimeException("参数链路规则校验失败");
                }
            }
        }
    }

    /**
     * 节点链路处理
     * @param linkItem
     * @param list
     * @param linkItems
     */
    private static void linkItemHandler(LinkItem linkItem, List<Link> list, List<LinkItem> linkItems) {

        // 场景1: pre-无,next-无
        if (CollUtil.isEmpty(linkItem.getPre()) && CollUtil.isEmpty(linkItem.getNext())) {
            Link link = new Link();
            List<Integer> itemIds = new ArrayList<>();
            itemIds.add(linkItem.getId());
            link.setItemIds(itemIds);
            list.add(link);
            return;
        }

        // 场景2:pre-无, next-有,开始节点,链路中需要添加当前节点和next节点
        if (CollUtil.isEmpty(linkItem.getPre()) && CollUtil.isNotEmpty(linkItem.getNext())) {
            for (Integer id : linkItem.getNext()) {
                Link link = new Link();
                List<Integer> itemIds = new ArrayList<>();
                itemIds.add(linkItem.getId());
                itemIds.add(id);
                link.setItemIds(itemIds);
                list.add(link);
            }
            return;
        }

        // 场景3:pre-有, next-无,终止节点,链路无需处理
        if (CollUtil.isNotEmpty(linkItem.getPre()) && CollUtil.isEmpty(linkItem.getNext())) {
            // 由于终止节点已经在场景4里面添加了,所以此处无需任何处理
            return;
        }

        // 场景4: pre-有, next-有,中间节点,由于当前节点已经在场景2添加过了,此时只需要添加next节点
        if (CollUtil.isNotEmpty(linkItem.getPre()) && CollUtil.isNotEmpty(linkItem.getNext())) {

            if(CollUtil.isEmpty(list)) {
                throw new RuntimeException("参数校验失败,中间节点链路不可为空");
            }

            List<Link> newList = new ArrayList<>();
            List<Link> removeList = new ArrayList<>();

            // 先找出该节点对应的链路
            for (Link link : list) {

                List<Integer> itemIds = link.getItemIds();
                Integer id = link.getItemIds().get(itemIds.size() - 1);

                if (id.equals(linkItem.getId())) {

                    for (int i = 0; i < linkItem.getNext().size(); i++) {

                        // 校验当前节点是否合法
                        itemIdIsValid(linkItem.getNext().get(i), link, linkItems);

                        // 删除原来的链路
                        removeList.add(link);

                        // 补充一个新的链路,并将该节点的next节点加入链路中
                        Link newLink = new Link();
                        List<Integer> newRuleItems = new ArrayList<>();
                        newRuleItems.addAll(link.getItemIds());
                        newRuleItems.add(linkItem.getNext().get(i));
                        newLink.setItemIds(newRuleItems);
                        newList.add(newLink);
                    }
                }
            }

            if (CollUtil.isNotEmpty(newList)) {
                list.removeAll(removeList);
                list.addAll(newList);
            }
        }
    }

    
}

Link:

@Data
public class Link {
    private List<Integer> itemIds;
}

结果验证

该数据来源于本文开头的示意图。

public static void main(String[] args) {

    List<LinkItem> linkItems = new ArrayList<>();

    linkItems.add(new LinkItem(1, null, Arrays.asList(2, 6)));
    linkItems.add(new LinkItem(2, Arrays.asList(1, 6), Arrays.asList(3, 7)));
    linkItems.add(new LinkItem(3, Arrays.asList(2, 10), Arrays.asList(4, 12)));
    linkItems.add(new LinkItem(4, Arrays.asList(3, 11), Arrays.asList(5, 7)));
    linkItems.add(new LinkItem(5, Arrays.asList(4, 8, 12), null));
    linkItems.add(new LinkItem(6, Arrays.asList(1), Arrays.asList(2)));
    linkItems.add(new LinkItem(7, Arrays.asList(2, 4), Arrays.asList(8)));
    linkItems.add(new LinkItem(8, Arrays.asList(7), Arrays.asList(5)));
    linkItems.add(new LinkItem(9, null, Arrays.asList(10, 13)));
    linkItems.add(new LinkItem(10, Arrays.asList(9), Arrays.asList(3)));
    linkItems.add(new LinkItem(11, Arrays.asList(12), Arrays.asList(4)));
    linkItems.add(new LinkItem(12, Arrays.asList(3), Arrays.asList(5, 11)));
    linkItems.add(new LinkItem(13, Arrays.asList(9), Arrays.asList(15, 17)));
    linkItems.add(new LinkItem(15, Arrays.asList(13), Arrays.asList(16)));
    linkItems.add(new LinkItem(16, Arrays.asList(15), Arrays.asList(17)));
    linkItems.add(new LinkItem(17, Arrays.asList(13, 16), null));
    linkItems.add(new LinkItem(18, null, null));
    linkItems.add(new LinkItem(19, null, Arrays.asList(20)));
    linkItems.add(new LinkItem(20, Arrays.asList(19), null));

    List<Link> links = getAllLink(linkItems);
    for (Link link : links) {
        log.info("{}", JSON.toJSONString(link));
    }
}

运行结果:

截图红框就是本文示意图的所有链路。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2110933.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Python】pip install速度太慢的多种解决方案

目录 问题描述为什么 pip 速度这么慢&#xff1f;解决方案1. 使用国内镜像源2. 配置多个镜像源3. 使用第三方工具4. 手动下载后本地安装5. 优化网络环境6. 更新 pip 版本 测试效果 问题描述 在使用 Python 进行开发时&#xff0c;我们经常需要使用 pip 来安装第三方库。然而&am…

【项目二】C++高性能服务器开发——日志系统(日志器,日志级别,日志事件)

知识点备忘录 其实也没啥 操作记录 在乌邦图上写的&#xff0c;先是模仿sylar创建了目录 进入sylar文件夹&#xff0c;有今天写的log.h头文件 其中log_test.cpp是为了测试log.h能否正常运行建的&#xff0c;只是个测试文件 log.h写了三个类&#xff0c;日志级别&#xff0…

计算机毕业设计选题推荐-果蔬生产溯源管理系统-Java/Python

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

C++笔记19•数据结构:红黑树(RBTree)•

红黑树 1.简介&#xff1a; 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c;红黑树确保没有一条路 径会比其他路径长出俩倍…

F12抓包05:Network接口测试(抓包篡改请求)

课程大纲 使用线上接口测试网站演示操作&#xff0c;浏览器F12检查工具如何进行简单的接口测试&#xff1a;抓包、复制请求、篡改数据、发送新请求。 测试地址&#xff1a;https://httpbin.org/forms/post ① 抓包&#xff1a;鼠标右键打开“检查”工具&#xff08;F12&#xf…

卷积神经网络(一)

目录 一.卷积神经网络的组成 二.卷积层 目的: 参数: 计算公式 卷积运算过程 三.padding-零填充 1.Valid and Same卷积 2.奇数维度的过滤器 四.stride步长 五.多通道卷积 1.多卷积核(多个Filter) 六.卷积总结 七.池化层(Pooling) 八.全连接层 都看到这里了,点个…

Lanenet - 实时车道线检测系统

基于深度学习的实时车道线检测&#xff0c;lanenet,tensorflow框架&#xff0c;有界面&#xff0c;可以检测图像和视频等。 项目名称&#xff1a;Lanenet - 实时车道线检测系统 项目概述 Lanenet 是一个利用深度学习技术进行实时车道线检测的系统。该系统旨在帮助驾驶员在行驶…

分享7款实现社会实践报告AI生成论文网站

在当今社会&#xff0c;AI技术的快速发展极大地改变了我们的生活方式和工作方式。特别是在学术研究和写作领域&#xff0c;AI工具的应用已经变得越来越普遍。本文将详细介绍7款实现社会实践报告AI生成的论文网站&#xff0c;并重点推荐千笔-AIPassPaper。 1. 千笔-AIPassPaper…

【Pytorch】加载数据

数据集获取&#xff1a;链接: https://pan.baidu.com/s/1jZoTmoFzaTLWh4lKBHVbEA 密码: 5suq 本文基于P5. PyTorch加载数据初认识_哔哩哔哩_bilibili dataset&#xff1a;提供一种方式去获取数据及其label值&#xff0c;解释&#xff1a;Pytorch中的dataset类——创建适应任意…

使用rsyslog转发自定义日志到指定服务器

rsyslog简介 rsyslog 是一个高度可配置的、功能强大的系统日志守护进程&#xff0c;广泛用于 UNIX 和 Linux 系统中。它是 syslog 的一个扩展版本&#xff0c;提供了许多额外的功能和改进。能够收集、过滤、存储和转发日志数据。它的灵活性和扩展性使其成为现代 Linux 系统中日…

剪辑视频,这四大工具助你一臂之力!

在这个数字化的时代&#xff0c;视频已成为一种重要的表达手段。无论您是专业视频制作者还是只是偶尔想要编辑一些个人视频&#xff0c;一款优秀的视频剪辑软件都将是您不可或缺的好帮手。以下是几款值得推荐的视频剪辑软件。 福昕视频剪辑 直达链接&#xff1a;www.pdf365.c…

巧用智能表单高效收集客户信息

企客宝企微版的智能表单功能&#xff0c;方便企微好友提交信息&#xff0c;直接入库管理&#xff0c;确保了一些类似身份证号等敏感信息&#xff0c;在传递过程中的数据安全 前言 很多企业在与企微好友沟通时&#xff0c;有时会有收集客户信息的需求&#xff0c;比如客户报名的…

PID控制算法(一)

PID算法控制&#xff1a; PID算法应用十分广泛&#xff0c;包括温度、气压控制&#xff0c;流速、液位控制&#xff0c;无人机悬停&#xff0c;小球摆动受力以及姿态调整等等。 此时&#xff0c;假定有固定时间间隔t&#xff0c;对应有不同t时刻的输出值x&#xff1b;另外&…

【机器学习】生成对抗网络(Generative Adversarial Networks, GANs)详解

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 生成对抗网络(Generative Adversarial Networks, GANs)详解GANs的基本原理GANs的…

基于人工智能的情感分析系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 情感分析系统是自然语言处理中的重要应用之一&#xff0c;用于从文本中自动识别和分析用户情感&#xff0c;如“积极”、“消极”或“…

UDP通信实现

目录 前言 一、基础知识 1、跨主机传输 1、字节序 2、主机字节序和网络字节序 3、IP转换 2、套接字 3、什么是UDP通信 二、如何实现UDP通信 1、socket():创建套接字 2、bind():绑定套接字 3、sendto():发送指定套接字文件数据 4、recvfrom():接收指定地址信息的数据 三…

C语言操作符汇总(上)

目录 前言 一、操作符的分类 二、⼆进制和进制转换 1. 二进制转10进制 2. 10进制转2进制数字 3. 2进制转8进制和16进制 3.1 2进制转8进制 3.2 二进制转16进制 三、原码、反码、补码 四、移位操作符 1. 左移操作符 2. 右移操作符 五、位操作符&#xff1a;&…

10-1RT-Thread动态内存管理

10-1RT-Thread动态内存管理 在嵌入式系统中&#xff0c;变量和中间数据一般存放在系统存储空间中。只有在实际使用时&#xff0c;才将它们从存储空间读取到CPU进行运算。存储空间可分为两种&#xff0c;内部存储空间rem和外部存储空间rome或flash。其中ram或称之为内存&…

【Linux详解】命令行参数|环境变量

目录 一、命令行参数 二、环境变量 1.环境变量的基本概念 2.查看环境变量的方法 3.环境变量相关命令 4.环境变量的组织方式以及获取环境变量的三种方法 环境变量具有全局属性 一、命令行参数 【示例1】main函数也是函数&#xff0c;main函数可以带参吗&#xff1f; 没…

Python教程(二十) : 十分钟入门【PyQt6】

文章目录 专栏列表环境准备1 安装 Python2 安装 PyQt6 PyQt6 中的模块使用模块创建一个窗体&#xff1a; PyQt6 常用的控件1. QPushButton&#xff08;按钮&#xff09;2. QLabel&#xff08;标签&#xff09;3. QLineEdit&#xff08;文本输入框&#xff09;4. QTextEdit&…