Android 启动优化(二) – 有向无环图的原理以及解题思路

Android 启动优化(一) - 有向无环图

Android 启动优化(二) - 拓扑排序的原理以及解题思路

Android 启动优化(三) - AnchorTask 使用说明

Android 启动优化(四)- 手把手教你实现 AnchorTask

Android 启动优化(五)- AnchorTask 1.0.0 版本更新了

Android 启动优化(六)- 深入理解布局优化

这几篇文章从 0 到 1,讲解 DAG 有向无环图是怎么实现的,以及在 Android 启动优化的应用。

推荐理由:现在挺多文章一谈到启动优化,动不动就聊拓扑结构,这篇文章从数据结构到算法、到设计都给大家说清楚了,开源项目也有非常强的借鉴意义。

前言

春节之前,更新了一篇博客 Android 启动优化(一) - 有向无环图,反响还不错,今天,让我们一起来看一下,怎样用代码实现有向无环图。

基本概念

拓扑排序的英文名是 Topological sorting。

拓扑排序要解决的问题是给一个图的所有节点排序。有向无环图才有拓扑排序,非有向无环图没有。

换句话说,拓扑排序必须满足以下条件

图必须是一个无环有向图。序列必须满足的条件:

  • 每个顶点出现且只出现一次。
  • 若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。

实战

我们已 leetcode 上面的一道算法题目作为切入点进行讲解。

leeocode 210: https://leetcode-cn.com/problems/course-schedule-ii/

eg: 现在你总共有 n 门课需要选,记为 0 到 n-1。

在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]

给定课程总量以及它们的先决条件,返回你为了学完所有课程所安排的学习顺序。

可能会有多个正确的顺序,你只要返回一种就可以了。如果不可能完成所有课程,返回一个空数组。

示例 1

输入: 2, [[1,0]] 
输出: [0,1]
解释: 总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1] 。

示例 2

输入: 4, [[1,0],[2,0],[3,1],[3,2]]
输出: [0,1,2,3] or [0,2,1,3]
解释: 总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。
     因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3] 。

这道题,很明显,看起来可以有有向无环图的解法来解决

BFS 算法

题目分析

我们首先引入有向图 描述依赖关系

示例:假设 n = 6,先决条件表:[ [3, 0], [3, 1], [4, 1], [4, 2], [5, 3], [5, 4] ]

  • 0, 1, 2 没有先修课,可以直接选。其余的,都要先修 2 门课
  • 我们用 有向图 描述这种 依赖关系 (做事的先后关系):
    Android 启动优化(二) -  有向无环图的原理以及解题思路

在有向图中,我们知道,有入度出度概念:

如果存在一条有向边 A --> B,则这条边给 A 增加了 1 个出度,给 B 增加了 1 个入度。所以顶点 0、1、2 的 入度为 0。 顶点 3、4、5 的 入度为 2

BFS 前准备工作

  • 我们关心 课程的入度 —— 该值要被减,要被监控
  • 我们关心 课程之间的依赖关系 —— 选这门课会减小哪些课的入度
  • 因此我们需要合适的数据结构,去存储这些关系,这个可以通过哈希表

解题思路

  • 维护一个 queue,里面都是入度为 0 的课程
  • 选择一门课,就让它出列,同时 查看哈希表,看它 对应哪些后续课
  • 将这些后续课的 入度 - 1,如果有减至 0 的,就将它推入 queue
  • 不再有新的入度 0 的课入列 时,此时 queue 为空,退出循环
    private  class Solution {
        public int[] findOrder(int num, int[][] prerequisites) {

            // 计算所有节点的入度,这里用数组代表哈希表,key 是 index, value 是 inDegree[index].实际开发当中,用 HashMap 比较灵活
            int[] inDegree = new int[num];
            for (int[] array : prerequisites) {
                inDegree[array[0]]++;
            }

            // 找出所有入度为 0 的点,加入到队列当中
            Queue<Integer> queue = new ArrayDeque<>();
            for (int i = 0; i < inDegree.length; i++) {
                if (inDegree[i] == 0) {
                    queue.add(i);
                }
            }
            
            ArrayList<Integer> result = new ArrayList<>();
            while (!queue.isEmpty()) {
                Integer key = queue.poll();
                result.add(key);
                // 遍历所有课程
                for (int[] p : prerequisites) {
                    // 改课程依赖于当前课程 key
                    if (key == p[1]) {
                        // 入度减一
                        inDegree[p[0]]--;
                        if (inDegree[p[0]] == 0) {
                            queue.offer(p[0]); // 加入到队列当中
                        }
                    }
                }
            }

            // 数量不相等,说明存在环
            if (result.size() != num) {
                return new int[0];
            }

            int[] array = new int[num];
            int index = 0;
            for (int i : result) {
                array[index++] = i;

            }

            return array;
        }
    }

DFS 解法

算法思想

  • 对图执行深度优先搜索。
  • 在执行深度优先搜索时,若某个顶点不能继续前进,即顶点的出度为0,则将此顶点入栈。
  • 最后得到栈中顺序的逆序即为拓扑排序顺序。
// 方法 2:邻接矩阵 + DFS   由于用的数组,每次都要遍历,效率比较低
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        if (numCourses == 0) return new int[0];
        // 建立邻接矩阵
        int[][] graph = new int[numCourses][numCourses];
        for (int[] p : prerequisites) {
            graph[p[1]][p[0]] = 1;
        }
        // 记录访问状态的数组,访问过了标记 -1,正在访问标记 1,还未访问标记 0
        int[] status = new int[numCourses];
        Stack<Integer> stack = new Stack<>();  // 用栈保存访问序列
        for (int i = 0; i < numCourses; i++) {
            if (!dfs(graph, status, i, stack)) return new int[0]; // 只要存在环就返回
        }
        int[] res = new int[numCourses];
        for (int i = 0; i < numCourses; i++) {
            res[i] = stack.pop();
        }
        return res;
    }

    private boolean dfs(int[][] graph, int[] status, int i, Stack<Integer> stack) {
        if (status[i] == 1) return false; // 当前节点在此次 dfs 中正在访问,说明存在环
        if (status[i] == -1) return true;

        status[i] = 1;
        for (int j = 0; j < graph.length; j++) {
            // dfs 访问当前课程的后续课程,看是否存在环
            if (graph[i][j] == 1 && !dfs(graph, status, j, stack)) return false;
        }
        status[i] = -1;  // 标记为已访问
        stack.push(i);
        return true;
    }

总结

这篇博客从实战的角度出发,介绍了有向无环图的两种解法,入度表法和 DFS 法。其中,入度表法很重要,必须掌握。下一篇,我们将从 项目实战的角度来讲解,怎样搭建一个有向无环图的通用框架,敬请期待。

ps

AnchorTask 源码已经更新到 github,AnchorTask,下一篇,将输出 AnchorTask 使用说明,敬请期待。

如果你觉得对你有所帮助,可以关注我的微信公众号徐公

Android 启动优化(二) -  有向无环图的原理以及解题思路

原文链接:https://www.cnblogs.com/gdutxiaoxu/p/17145999.html

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Android 启动优化(二) – 有向无环图的原理以及解题思路 - Python技术站

(0)
上一篇 2023年4月18日
下一篇 2023年4月18日

相关文章

  • 关于RecycleView的子元素锁定吸顶效果

      最近有一关于列表元素锁定吸顶的需求。处理方案比较简单的,在外层布局添加一个相同的标题栏控件,根据控件在滚动过中的位置来判断显示隐藏,从而达到效果。使用的关键代码是: int[] loc = new int[2]; view.getLocationOnScreen(loc); int[] fitLoc = new int[2]; outView.getLo…

    Android 2023年4月18日
    00
  • Android报”NullPointerException”如何解决?

    当Android应用程序运行时,如果遇到无法解析的引用或访问已被销毁的对象时,会报 NullPointerException 异常。通常NullPointerException是由于一些常见的编程错误导致的。例如,尝试在null对象上调用方法或访问属性,或者使用未初始化的变量。 在Android应用程序的开发过程中,NullPointerException异…

    Android 2023年4月3日
    00
  • 运动健康路线导入,助力用户轻松导航

    华为HMS Core运动健康服务支持通过REST API,以GPX文件格式写入用户路线数据,支持导入轨迹(Track)或路程(Route)类型的数据,实现用户路线数据在华为运动健康App中的展示效果。 假若与华为运动健康App相连接的穿戴设备支持路线导入,那么用户路线数据将自动下发至穿戴设备。用户可使用手表轻松导航,按照既定路线进行跑步、爬山等活动。(当前支…

    Android 2023年4月17日
    00
  • 音频编辑服务UI SDK接入指导及常见问题

    华为 HMS Core 音频编辑服务(Audio Editor Kit)是华为帮助全球开发者快速构建各类应用音频能力的服务,汇聚了华为在音乐、语音等相关音频领域的先进技术。音频编辑服务为开发者们提供音频基础编辑、AI配音、音源分离、空间渲染、变声、多种音效等丰富的音频处理能力,以及性能优异、简单易用、开放性强的接口,开发者们可依据应用场景,在App中高效轻松…

    Android 2023年4月18日
    00
  • 安卓ro.serialno产生的整个流程

    前言: 关于ro.serialno这个属性,相信大家都不陌生了,应用层的Build.getSerial(),Build.SERIAL等均是直接或间接的获取了这个属性值。接下来从boot到系统应用,小小的分析一下它的整个流程: 由于是APP经常使用,那我们从应用层分析到底层kernel/boot 一,framework层 好的,我们进入安卓源码目录,grep查…

    Android 2023年4月18日
    00
  • 汽车之家Unity前端通用架构升级实践

    背景介绍 随着之家3D虚拟化需求的增加,各产品线使用Unity引擎的项目也越来越多,新老项目共存,代码维护成本也随之增加。代码质量参差加之代码规范仍没有完全统一产生高昂学习成本进一步加重了项目维护负担。 为应对这些问题,我们决定借助主机厂数科产品线销冠神器VR版本大升级为契机,开发一套移动端通用Unity代码框架,旨在统一Unity项目开发流程和规范,使不同…

    Android 2023年5月9日
    00
  • 扫一扫,原来这么简单

    二维码识别技术已广泛应用在移动支付、实用工具、电商购物、社交通讯等场景。然而,在实际生活中,二维码容易遇到距离远、暗光、强光、污损、模糊和大角度倾斜等复杂场景,导致识别困难,扫码体验差。华为HMS Core 统一扫码服务(Scan Kit)为开发者们的APP带来一站式扫码解决方案,并且拥有高识别率和快速识别等特点。 距离太远、码图过小? 在停车场扫码缴费、上…

    Android 2023年4月17日
    00
  • 无线电发射设备管理规定

    2022年12月22日工业和信息化部令第57号公布,自2023年7月1日起施行 主要总结为以下几点: 1.型号核准证的有效期不短于2年,不超过5年,型号核准证每次延续的有效期不超过5年 2.微功率短距离无线电发射设备使用说明中应当注明“使用微功率短距离无线电发射设备应当符合国家无线电管理有关规定”字样。 3.型号核准代码应当在无线电发射设备上标注或者采用电子…

    Android 2023年4月18日
    00
合作推广
合作推广
分享本页
返回顶部