【算法基础】拓扑排序及实战

news2024/11/26 13:40:36

一 、概览

这里涉及到图的概念,感兴趣的同学请移驾 –>图<–
下面还有两个相关概念,大概说一下:

1.1 有向无环图

定义:在图论中,如果一个有向图从任意顶点出发无法经过若干条边回到该点,则这个图是一个有向无环图(DAG,Directed Acyclic Graph)
每条边都带有从一个顶点指向另一个顶点的方向的图为有向图。有向图中的道路为一系列的边,系列中每条边的终点都是下一条边的起点。
如果一条路径的起点是这条路径的终点,那么这条路径就是一个环。有向无环图即为没有环出现的有向图

1.2 拓扑结构

定义:将实体抽象成点,将实体间的链接抽象成线,进而以图形的关系呈现这些点与线之间的关系。其目的在于研究这些点、线之间的相连关系。表示点和线之间关系的图被称为拓扑结构 图

比较常用的是网络拓扑结构
在这里插入图片描述

背景:
一个较大的工程往往被划分成许多子工程,我们把这些子工程称作活动(activity)。在整个工程中,有些子工程(活动)必须在其它有关子工程完成之后才能开始,也就是说,一个子工程的开始是以它的所有前序子工程的结束为先决条件的,但有些子工程没有先决条件,可以安排在任何时间开始。为了形象地反映出整个工程中各个子工程(活动)之间的先后关系,可用一个有向图来表示,图中的顶点代表活动(子工程),图中的有向边代表活动的先后关系,即有向边的起点的活动是终点活动的前序活动,只有当起点活动完成之后,其终点活动才能进行。通常,我们把这种顶点表示活动、边表示活动间先后关系的有向图称做顶点活动网(Activity On Vertex network),简称AOV网。

二、拓扑排序(顶点的线性排序)

定义:在图论中,拓扑排序(Topological Sorting)是一个有向无环图(DAG, Directed Acyclic Graph)的所有顶点的线性序列

例如,一个项目包括A、B、C、D四个子部分来完成,并且A依赖于B和D,C依赖于D。现在要制定一个计划,写出A、B、C、D的执行顺序。这时,就可以利用到拓扑排序,它就是用来确定事物发生的顺序的。

且该序列必须满足下面两个条件:

  • 每个顶点出现且只出现一次。
  • 若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。
  • 有向无环图(DAG)才有拓扑排序

度数: 由一个顶点出发,有几条边就称该顶点有几度,或者该顶点的度数是几
出度: 由一个顶点出发的边的总数
入度: 指向一个顶点的边的总数

拓扑排序使用深度优先算法,时间复杂度为O ( V + E )

拓扑排序通常有几种实现方式:

2.1 入度表(直接遍历)

在这里插入图片描述
于是,得到拓扑排序后的结果是 { 1, 2, 4, 3, 5 }。
通常,一个有向无环图可以有一个或多个拓扑排序序列。

2.2 通过DFS(深度)和栈实现

思路:

找到顶点,递归遍历到最后的结点,通过回溯将遍历到的点入栈,那么先进栈的必定是只有入度的结点,只有出度的结点必定在最后进栈,最后通过出栈可以得到排序后的顺序。

具体代码请看 实战 2

2.3 通过队列实现

思路:

  • 通过遍历,将所有入度为0的进入队列。并将与之相连的结点的入度-1。
  • 然后一个一个的出队列,出队列的同时判断与出队列结点相连的结点是否入度为0,为0则进栈。
  • 循环第一二步,直到所有节点被选择或者栈空。(其实栈空的时候,所有节点就是被选择了)

不废话,直接上代码:

/**
 * 图的存储
 * 邻接矩阵 二维数组
 */
public static class GrapMatrix {


    /**
     * 节点个数
     */
    public int size;

    /**
     * 顶点名称
     */
    char [] nodeName;

    /**
     * 排序后的顺序
     */
    List result;

    /**
     * 图关系矩阵
     */
    int [][] matrix;

    /**
     *
     * @param nodeName 节点
     * @param edgs 节点关系
     */
    public GrapMatrix(char[] nodeName, char[][] edgs) {
        size = nodeName.length;
        this.nodeName = nodeName;
        // 设置图关系矩阵大小
        this.matrix = new int[size][size];
        result = new ArrayList<Integer>();

        // 初始化图关系矩阵
        for (char[] node: edgs) {
            matrix[getPosition(node[0])][getPosition(node[1])] = 1;
            System.out.println(node);
        }

        // 输出图的邻接矩阵
        for(int i = 0; i < size; i ++) {
            for (int j = 0; j < size; j ++) {
                System.out.print(matrix[i][j] + " ");
            }
            System.out.println("");
        }
    }

    // 排序
    public void tuopuSort() {
        System.out.println("\n");
        // 一个一维数组,用来保存顶点的入度
        int indegree[] = new int[size];
        boolean indegreeV[] = new boolean[size];


        // 给入度输入值
        for(int i = 0; i < size; i ++) {
            indegreeV[i] = false;
            for (int j = 0; j < size; j ++) {
                if (matrix[i][j] == 1) {
                    indegree[j] = indegree[j] + 1;
                }
            }
        }
        System.out.println("\n");

        //开始进行遍历
        LinkedList<String> nodes = new LinkedList<String>();

        // 将入度为 0 的节点入队列
        for (int x = 0; x < size; x ++) {
            if (indegree[x] == 0) {
                System.out.println(nodeName[x]);
                nodes.add(String.valueOf(nodeName[x]));
            }
        }

        int j = 0;
        while (!nodes.isEmpty()) {
            for (int x = 0; x < size; x ++) {
                System.out.println("\n 数组 x = " + x + ", ");
                if (indegree[x] == 0 && !indegreeV[x]) {

                    indegreeV[x] = true;
                    String s = nodes.poll();
                    System.out.println("add = " +s);
                    result.add(s);

                    // 找到跟它相关的节点,,入度 -1
                    for (int y = 0; y < size; y ++) {
                        if (matrix[x][y] == 1) {
                            System.out.println("相关的节点 -1 = " + y);
                            indegree[y] = indegree[y] - 1;
                            if (indegree[y] == 0) {
                                System.out.println("相关的节点 -1 后, 入度为0, " + nodeName[y]);
                                nodes.add(String.valueOf(nodeName[y]));
                            }
                        }
                    }
                } else {

                }
            }
            j ++;
        }


        System.out.println(result);

    }

    public int getPosition(char pos) {
        for (int i = 0; i < nodeName.length; i ++) {
            if (nodeName[i] == pos) {
                return i;
            }
        }
        return -1;
    }
}

三、实战

应用:
拓扑排序通常用来“排序”具有依赖关系的任务。
eg: 关键路径
选课系统
等这些任务有先后顺序的图。

      比如,要想升职加薪,就要先拍马屁

3.1 选课系统

我们现在以课程排序来做代码测试,
假定一个计算机专业的学生必须完成下图所列出的全部课程。
在这里,课程代表活动,学习一门课程就表示进行一项活动,学习每门课程的先决条件是学完它的全部先修课程。
在这里插入图片描述
我们用图的方式,将他们的先后顺序及依赖关系表示如下:
在这里插入图片描述
对于—> 图 的存储结构,常用的是"邻接矩阵"和"邻接表",

拓扑排序的动态表示
https://www.cs.usfca.edu/~galles/visualization/TopoSortIndegree.html

3.2 Android冷启动优化,有向无环图启动器

Application中初始化应用所需的业务、工具、UI等组件,导致耗时,导致冷启动会比较慢,需要进行优化处理,
在这里插入图片描述
我们要做的就是把主线程的串行任务变成并发任务,在将所有任务整理出来后,进行一个排序,

1、每一个业务模块当成一个任务,再梳理任务之间的关系。有的必须要在所以任务之前初始化,有的必须要在主线程初始化,有的可以有空在初始化,有的必须要在有的任务执行完毕再初始化,将这些任务的先后顺序及依赖关系用图画出来。
在这里插入图片描述

主进程执行, eg:推送,就不需要判断进程
主线程执行,eg:有的要主线程,有的要子线程

2、代码Task化,启动逻辑抽象为Task;
3、根据所有任务依赖关系排序生成一个有向无环图;
4、多线程按照排序后的优先级依次执行

关健代码

public class AppStartTaskSortUtil {
    /**
     * 拓扑排序
     * taskIntegerHashMap每个Task的入度(key= Class < ? extends AppStartTask>)
     * taskHashMap每个Task            (key= Class < ? extends AppStartTask>)
     * taskChildHashMap每个Task的孩子  (key= Class < ? extends AppStartTask>)
     * deque 入度为0的Task
     */
    public static List<AppStartTask> getSortResult(List<AppStartTask> startTaskList, HashMap<Class<? extends AppStartTask>, AppStartTask> taskHashMap, HashMap<Class<? extends AppStartTask>, List<Class<? extends AppStartTask>>> taskChildHashMap) {
        List<AppStartTask> sortTaskList = new ArrayList<>();
        HashMap<Class<? extends AppStartTask>, TaskSortModel> taskIntegerHashMap = new HashMap<>();
        Deque<Class<? extends AppStartTask>> deque = new ArrayDeque<>();
        for (AppStartTask task : startTaskList) {
            if (!taskIntegerHashMap.containsKey(task.getClass())) {
                taskHashMap.put(task.getClass(), task);
                taskIntegerHashMap.put(task.getClass(), new TaskSortModel(task.getDependsTaskList() == null ? 0 : task.getDependsTaskList().size()));
                taskChildHashMap.put(task.getClass(), new ArrayList<Class<? extends AppStartTask>>());
                //入度为0的队列
                if (taskIntegerHashMap.get(task.getClass()).getIn() == 0) {
                    deque.offer(task.getClass());
                }
            } else {
                throw new RuntimeException("任务重复了: " + task.getClass());
            }
        }
        //把孩子都加进去
        for (AppStartTask task : startTaskList) {
            if (task.getDependsTaskList() != null) {
                for (Class<? extends AppStartTask> aclass : task.getDependsTaskList()) {
                    taskChildHashMap.get(aclass).add(task.getClass());
                }
            }
        }
        //循环去除入度0的,再把孩子入度变成0的加进去
        while (!deque.isEmpty()) {
            Class<? extends AppStartTask> aclass = deque.poll();
            sortTaskList.add(taskHashMap.get(aclass));
            for (Class<? extends AppStartTask> classChild : taskChildHashMap.get(aclass)) {
                taskIntegerHashMap.get(classChild).setIn(taskIntegerHashMap.get(classChild).getIn() - 1);
                if (taskIntegerHashMap.get(classChild).getIn() == 0) {
                    deque.offer(classChild);
                }
            }
        }
        if (sortTaskList.size() != startTaskList.size()) {
            throw new RuntimeException("出现环了");
        }
        return sortTaskList;
    }
}

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

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

相关文章

测试者必知—如何做Web测试?常见测试点总结

目录 前言&#xff1a; 一、Web应用程序 二、功能测试 三、易用性测试&#xff08;界面测试&#xff09; 四、兼容性测试 五、安全性测试 六、性能测试 前言&#xff1a; Web测试是指对基于Web技术的应用程序进行测试&#xff0c;以测试其功能、性能、安全和稳定性等方面的表…

C++使用boost::serialization进行序列化

废话 发现一个比较好玩的东西&#xff0c;boost::serialization序列化操作&#xff0c;简单来说感觉像ofstream和ifstream的升级版&#xff0c;Boost.Serialization 库能够将c项目中的对象转换为一序列的比特&#xff08;bytes&#xff09;&#xff0c;用来保存和加载还原对象…

PPT里的逆天功能之-PPT扣图(可以秒杀PS的扣图功能)

PPT的扣图功能秒杀PS PPT其实非常强大,它里面有数不甚数的功能是我们之前从未使用过的。比如说PPT的扣图功能。比如说我们有一个背景是灰黑的。 而我们网上可以下到的一些logo很多是带有底色的,它会和我们的背景形成很大的反差。此时我们往往就会用PS工具。这就会让大都IT们…

leetcode数据库题第四弹

leetcode数据库题第四弹 619. 只出现一次的最大数字620. 有趣的电影626. 换座位627. 变更性别1045. 买下所有产品的客户1050. 合作过至少三次的演员和导演1068. 产品销售分析 I1070. 产品销售分析 III1075. 项目员工 I1084. 销售分析III小结 619. 只出现一次的最大数字 https:/…

MySQL的JDBC编程详解

1.JDBC编程概念 在实际项目中&#xff0c;我们对于数据库的操作大部分是通过代码来完成的&#xff0c;各种数据库在开发的时候&#xff0c;都会提供一组编程接口API。所以不同公司使用不同的数据库软件&#xff0c;那么程序员学习成本是非常大的&#xff0c;因为要学习不同数据…

超好用的5款软件,每一款都让你爱不释手

分享爱&#xff0c;分享时光&#xff0c;分享精彩瞬间&#xff0c;大家好&#xff0c;我是互联网的搬运工&#xff0c;今天继续给大家带来几款好用的软件。 1.GIF录制——LICEcap ​ LICEcap是一款用于录制屏幕为GIF动画的工具。它可以让你用一个可调整的窗口来捕捉你的屏幕上…

2023年前端面试高频考点HTML5+CSS3

目录 浏览器的渲染过程⭐⭐⭐ CSS 、JS 阻塞 DOM 解析和渲染 回流&#xff08;重排&#xff09;和重绘⭐⭐ 选择器 ID选择器、类选择器、标签选择器&#xff08;按优先级高到低排序&#xff09;⭐⭐ 特殊符号选择器&#xff08;&#xff1e;,,~&#xff0c;空格&#xff0…

【JavaEE】网络编程之TCP套接字

目录 1、TCP套接字 1.1、ServerSocket 常用API 1.2、Socket 常用API 2、基于TCP套接字实现一个TCP回显服务器 2.1、服务器端代码 2.2、客户端代码 2.3、解决服务器不能同时和多个客户端建立链接的问题 3、基于TCP socket 写一个简单的单词翻译服务器 1、TCP套接字 T…

【中文编程】青语言

【引言】 青语言主页&#xff1a;https://qingyuyan.cn 青语言文档&#xff1a;https://doc.qingyuyan.cn 青语言社区&#xff1a;https://forum.qingyuyan.cn 青语言仓库&#xff1a;https://gitee.com/NjinN/Qing 长久以来&#xff0c;中文编程一直是开发者社区中争议不断的…

opencv读写png

[1] 测试了怎么手动加 alpha 通道设置透明度后&#xff0c;用 PIL.Image 存 png&#xff0c;通道顺序是 RGBA。这里测试用 opencv 读、写 3、4 通道的 png。 png 可以只存 3 通道的&#xff0c;即不要 alpha&#xff0c;也可以加上 alpha。而无 alpha 时 opencv 的通道顺序是 …

微信视频号加强打击肖像授权侵权短视频

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 视频号安全中心发布公告称&#xff1a;视频号将打击肖像权和侵权的短视频&#xff0c;并在7月份上线“视频授权功能”。 5月份视频号已经下架了3万多条视频&#xff0c;1万多个帐号减少推荐。你看3…

Spring框架-面试题核心概念

目录 1.Spring框架的作用是什么&#xff1f; 2. 什么是DI&#xff1f; 3.什么是AOP&#xff1f; 4.Spring常用注解 5.Spring中的设计模式 6.Spring支持的几种bean的作用域 7.Spring中Bean的生命周期&#xff1f; 8.Spring中的事务管理 9.Spring中的依赖注入方式有几种 10.Sprin…

SpringBoot项目热部署设置

目录 1.设置热部署的好处 2.设置热部署的坏处 3.设置热部署的流程 4.关闭热部署功能 1.设置热部署的好处 Spring Boot 热部署的主要好处是在开发过程中提高了开发效率和体验。它让开发者在修改代码后无需手动重启应用程序&#xff0c;而是可以快速自动重新加载应用程序&…

【新版】系统架构设计师 - 信息安全技术基础知识

个人总结&#xff0c;仅供参考&#xff0c;欢迎加好友一起讨论 文章目录 架构 - 信息安全技术基础知识考点摘要信息安全基础知识信息安全系统的组成框架信息加密技术对称加密&#xff08;共享密钥&#xff09;非对称加密&#xff08;公开密钥&#xff09;信息摘要数字签名数字信…

el-upload 多文件依次上传(防抖 + 递归)

需求描述 多图上传组件&#xff0c;1-9 张图选择完文件后自动上传&#xff0c;不需要上传按钮来进行手动上传 难点 接口有两种&#xff0c;多图集合上传接口 uploadImgs、单图上传接口 uploadImg 使用 uploadImgs 接口&#xff0c;参数为图片集合 fileList&#xff0c;但是缺…

Spring架构篇--2.7.4 远程通信基础--Netty原理--bind实现客户端acceptread事件处理

前言&#xff1a;本文在Netty 服务端已经实现NioServerSocketChannel 管道的初始化并且绑定了端口后&#xff0c;继续对客户端accept&read事件如何处理进行探究&#xff1b; 1 对客户端accept&read事件的触发&#xff1a; 从之前的ServerBootstrap 的bind 方法中似乎…

JMeter接口测试新思路——灵活使用BeanShell

目录 前言&#xff1a; BeanShell的简介 调用Java方法 执行Class文件 结合实际案例 总结 前言&#xff1a; 在JMeter进行接口测试时&#xff0c;我们可能会遇到需要调用Java方法或者执行Java代码的情况&#xff0c;这时候我们可以使用BeanShell来实现。BeanShell是一个类…

QuintoAndar 如何提高转化率

QuintoAndar 如何提高转化率 ——求关注、求点赞、求分享&#xff0c;二毛拜谢。 QuintoAndar 如何通过提高页面性能来提高每次会话的转化率和页面数 一个专注于优化 Core Web Vitals 并迁移到 Next.js 的项目使转换率提高了 5%&#xff0c;每个会话的页面增加了 87%。 Quint…

07.JavaWeb-Vue+elementUI

1.Vue 功能替代JavaScript和jQuery&#xff0c;基于JavaScript实现的前端框架 1.1配置Vue 1.1.1引入vue库 方法一&#xff1a;通过cdn链接引入最新版本的vue&#xff08;可能会慢些&#xff09; <head><script src"https://cdn.jsdelivr.net/npm/vue">…

基于yolov5开发构建道路路面病害检测识别系统——以捷克、印度、日本三国城市道路实况场景数据为例,开发对比分析模型并分析对应性能

城市道路病害检测是最近比较热门的一个任务领域&#xff0c;核心就是迁移深度学习目前已有的研究成果来实现实时城市道路路面病害的检测识别分析&#xff0c;在我之前的很多博文中都有做过类似桥梁、大坝、基建、隧道等水泥设施裂缝裂痕等目标检测相关的项目&#xff0c;除此之…