算法之拓扑关系

2023-10-31

目录

前言:

算法解析

Kahn算法 

DFS算法

总结:

参考资料


前言:

如何确定代码源文件的编译依赖关系?

我们知道,一个完整的项目往往会包含很多代码源文件。编译器在编译整个项目的时候,需要按照依赖关系,依次编译每个源文件。比如,A.cpp 依赖 B.cpp,那在编译的时候,编译器需要先编译 B.cpp,才能编译 A.cpp

编译器通过分析源文件或者程序员事先写好的编译配置文件(比如 Makefile 文件),来获取这种局部的依赖关系。那编译器又该如何通过源文件两两之间的局部依赖关系,确定一个全局的编译顺序呢?


算法解析

     这个问题的解决思路与这种数据结构的一个经典算法拓扑排序算法有关。那什么是拓扑排序呢?这个概念很好理解,我们先来看一个生活中的拓扑排序的例子。

      我们在穿衣服的时候都有一定的顺序,我们可以把这种顺序想成,衣服与衣服之间有一定的依赖关系。比如说,你必须先穿袜子才能穿鞋,先穿内裤才能穿秋裤。假设我们现在有八件衣服要穿,它们之间的两两依赖关系我们已经很清楚了,那如何安排一个穿衣序列,能够满足所有的两两之间的依赖关系?

  

 拓扑排序的原理非常简单,我们的重点应该放到拓扑排序的实现上面。

 算法是构建在具体的数据结构之上的。针对这个问题,我们先来看下,如何将问题背景抽象成具体的数据结构?

    我们可以把源文件与源文件之间的依赖关系,抽象成一个有向图。每个源文件对应图中的一个顶点,源文件之间的依赖关系就是顶点之间的边。

     如 a 先于 b 执行,也就是说 b 依赖于 a那么就在顶点 a 和顶点 b 之间,构建一条从 a 指向 b 的边。而且,这个图不仅要是有向图,还要是一个有向无环图,也就是不能存在像 a->b->c->a 这样的循环依赖关系。因为图中一旦出现环,拓扑排序就无法工作了。实际上,拓扑排序本身就是基于有向无环图的一个算法。

具体代码如下:


public class Graph {
  private int v; // 顶点的个数
  private LinkedList<Integer> adj[]; // 邻接表

  public Graph(int v) {
    this.v = v;
    adj = new LinkedList[v];
    for (int i=0; i<v; ++i) {
      adj[i] = new LinkedList<>();
    }
  }

  public void addEdge(int s, int t) { // s先于t,边s->t
    adj[s].add(t);
  }
}

    拓扑排序有两种实现方法,都不难理解。它们分别是 Kahn 算法和 DFS 深度优先搜索算法。我们依次来看下它们都是怎么工作的。

Kahn算法 

Kahn 算法实际上用的是贪心算法思想,思路非常简单、好懂。

定义数据结构的时候,如果 s 需要先于 t 执行,那就添加一条 s 指向 t 的边。所以,如果某个顶点入度为 0 也就表示,没有任何顶点必须先于这个顶点执行,那么这个顶点就可以执行了。

     我们先从图中,找出一个入度为 0 的顶点,将其输出到拓扑排序的结果序列中(对应代码中就是把它打印出来),并且把这个顶点从图中删除(也就是把这个顶点可达的顶点的入度都减 1)。我们循环执行上面的过程,直到所有的顶点都被输出。最后输出的序列,就是满足局部依赖关系的拓扑排序。

具体的代码实现如下:

 


public void topoSortByKahn() {
  int[] inDegree = new int[v]; // 统计每个顶点的入度
  for (int i = 0; i < v; ++i) {
    for (int j = 0; j < adj[i].size(); ++j) {
      int w = adj[i].get(j); // i->w
      inDegree[w]++;
    }
  }
  LinkedList<Integer> queue = new LinkedList<>();
  for (int i = 0; i < v; ++i) {
    if (inDegree[i] == 0) queue.add(i);
  }
  while (!queue.isEmpty()) {
    int i = queue.remove();
    System.out.print("->" + i);
    for (int j = 0; j < adj[i].size(); ++j) {
      int k = adj[i].get(j);
      inDegree[k]--;
      if (inDegree[k] == 0) queue.add(k);
    }
  }
}

DFS算法

       图上的深度优先搜索我们前面已经讲过了,实际上,拓扑排序也可以用深度优先搜索来实现。不过这里的名字要稍微改下,更加确切的说法应该是深度优先遍历,遍历图中的所有顶点,而非只是搜索一个顶点到另一个顶点的路径。

 具体的代码如下:


public void topoSortByDFS() {
  // 先构建逆邻接表,边s->t表示,s依赖于t,t先于s
  LinkedList<Integer> inverseAdj[] = new LinkedList[v];
  for (int i = 0; i < v; ++i) { // 申请空间
    inverseAdj[i] = new LinkedList<>();
  }
  for (int i = 0; i < v; ++i) { // 通过邻接表生成逆邻接表
    for (int j = 0; j < adj[i].size(); ++j) {
      int w = adj[i].get(j); // i->w
      inverseAdj[w].add(i); // w->i
    }
  }
  boolean[] visited = new boolean[v];
  for (int i = 0; i < v; ++i) { // 深度优先遍历图
    if (visited[i] == false) {
      visited[i] = true;
      dfs(i, inverseAdj, visited);
    }
  }
}

private void dfs(
    int vertex, LinkedList<Integer> inverseAdj[], boolean[] visited) {
  for (int i = 0; i < inverseAdj[vertex].size(); ++i) {
    int w = inverseAdj[vertex].get(i);
    if (visited[w] == true) continue;
    visited[w] = true;
    dfs(w, inverseAdj, visited);
  } // 先把vertex这个顶点可达的所有顶点都打印出来之后,再打印它自己
  System.out.print("->" + vertex);
}

      第一部分是通过邻接表构造逆邻接表。邻接表中,边 s->t 表示 s 先于 t 执行,也就是 t 要依赖 s。在逆邻接表中,边 s->t 表示 s 依赖于 ts 后于 t 执行。为什么这么转化呢?这个跟我们这个算法的实现思想有关。

     第二部分是这个算法的核心,也就是递归处理每个顶点。对于顶点 vertex 来说,我们先输出它可达的所有顶点,也就是说,先把它依赖的所有的顶点输出了,然后再输出自己。

总结:

    拓扑排序应用非常广泛,解决的问题的模型也非常一致。凡是需要通过局部顺序来推导全局顺序的,一般都能用拓扑排序来解决。除此之外,拓扑排序还能检测图中环的存在。对于 Kahn 算法来说,如果最后输出出来的顶点个数,少于图中顶点个数,图中还有入度不是 0 的顶点,那就说明,图中存在环。 

参考资料

本章内容来源于对王争大佬的《数据结构与算法之美》的专栏。

43 | 拓扑排序:如何确定代码源文件的编译依赖关系?-极客时间

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

算法之拓扑关系 的相关文章

随机推荐

  • python again_收藏!最全从Python小白到大牛,要走的路这里都有(初级篇)

    收藏 长文 从Python小白到大牛 要走的路这里都有面向项目的学习是学习编码的最佳方法 Python是当今最需求的语言 为了帮助您学习它 以下是一些您可以探索的最重要的Python项目 Python游戏Python图像编程CIFAR10在
  • 周鸿祎:什么是好的用户体验?

    说今天是一个体验为王的时代 一点也不过分 做大众消费品的人可能已经感觉到 今天消费者的话语权越来越强 如果你的产品做得好 不久就会口口相传 如果你的产品做得烂 不久就会骂声一片 所有这一切在过去是不可想象的 但今天 每个人都可以发布信息 每
  • hadoop任务执行时,报错

    2020 11 06 03 42 43 205 ERROR org apache hadoop yarn server resourcemanager scheduler SchedulerApplicationAttempt Error
  • Collectors.summing唯独没有BigDecimal的求和方法

    最近在做订单相关的模块 有个订单列表接口 需要对订单金额进行求和 每次都得遍历list 然后用BigDecimal add 方法取求和 感觉很麻烦 想到之前有用到java8的stream collect的Collectors summing
  • 一个例子搞懂 tabelu的上下文筛选器

    示例 1 将维度筛选器转换为上下文筛选器 本示例以及以下示例使用 Tableau Desktop 附带的 Sample Superstore 数据源 在此示例中 视图解决以下问题 按总销售额计 纽约市位居前 10 名的客户有哪些 视图包含两
  • Mono和MonoDevelop源码编译安装

    Mono和MonoDevelop源码编译安装 之所以用源码编译的方式安装mono和monodevelop 是因为通过yum安装的mono不是最新版本 而且monodevelop不能建 asp net MVC3的工程 而且通过源码安装 可以进
  • 世界坐标系、相机坐标系、图像坐标系、像素坐标系之间的转换及三维空间的刚体运动

    基本概念 https blog csdn net sunshine zoe article details 73457686 世界坐标系到相机坐标系下的变换 https www jianshu com p 64b4c887c439 通过两个
  • jquery中的伪数组和each和map静态方法区别,以及其他的一些静态方法

    伪数组 1 必须要有length属性 2 如果这个length的属性值是0 那么这个对象有没有元素无所谓 3 如果这个length的属性值不为0 那么这个对象一定头下标为 length 1 的属性值 列如 伪装组 var obj lengt
  • 公有链、联盟链、私有链区别

    1 公有链 公有链是世界上任何人都可以访问读取的 任何人都可以发送交易并且如果交易有效的话可以将之包括到区块中的 以及任何人都能够参与与其共识过程的区块 优点 所有交易数据公开 透明 无法篡改 缺点 低吞吐量 TPS 交易速度慢 2 联盟链
  • java橙色风格小说精品屋小说网站源码

    没有搭建教程 懂的自行下载研究 文件 590m com f 25127180 486121419 fbf84f 访问密码 551685 安装环境 宝塔面板 tomcat8 nginx1 17 mysql5 6 不知道最高支持到多少 打开服务
  • linux netLink检测usb插拔事件

    include
  • 从一到无穷大

    这本书一开始讲数学 后来讲到4围空间 空间压缩 相对论就头疼了 本不想读下去了 后来硬着头皮往下读 发现后面章节讲解生物 化学 天文等等 哈哈 原来是本科普的读物 大概都知道一些 不错 推荐给初高中生来看一看 2013 12 07
  • Linux内核之softirq机制

    中断的上半部执行紧要的任务 下半部则可以处理数据等次要的任务 tasklet也是基于softirq实现的 不同于tasklet的是 softirq是kernel编译时静态分配的 所以如果动态创建softirq或者kill softirqs
  • java 加密知识整理(未完)

    以下为网络转载 如有侵权请告知以便改正 JKS JCEKS PKCS12 BKS UBER 转一篇文章 http hi baidu com beyond456 blog item a3a009d7479b44d9a044df32 html
  • 计算机网络自顶向下WireShark实验:IP

    前言 计算机网络自顶向下WireShark实验记录 可供参考 题目 1 Select the first ICMP Echo Request message sent by your computer and expandthe Inter
  • 第二十六篇 DenseNet实战

    文章目录 摘要 1 项目结构 2 划分训练集和测试集 3 计算mean和Standard 3 1 标准化的作用 3 2 归一化的作用 4 Mixup CutMix CutOut数据集增强 5 训练
  • Windows应用-C#使用命令行执行PowerShell脚本

    前言 类似于bat脚本 能够自动执行一些任务 但是对bat不熟悉 因此选择使用C 来实现 具体是能够通过执行特定的语句实现对文件的读写与执行 代码 using System using System Collections Generic
  • 排序算法-堆排序

    思路 堆排序 Heapsort 是指利用堆积树 堆 这种数据结构所设计的一种排序算法 它是选择排序的一种 它是通过堆 来进行选择数据 需要注意的是排升序要建大堆 排降序建小堆 我们先将要排序的数据建成堆 然后通过下图所示的步骤进行排序 特性
  • 调试经验 - Fat文件打开失败的问题

    问题描述 在M7上调试打开SD卡中的某个文件 并读取内容的过程中 发现在SD卡中存在的一个文件firmware bin 通过open接口访问时 出现访问失败的问题 排查过程 发现f open 中传入的open flag为FA READ FA
  • 算法之拓扑关系

    目录 前言 算法解析 Kahn算法 DFS算法 总结 参考资料 前言 如何确定代码源文件的编译依赖关系 我们知道 一个完整的项目往往会包含很多代码源文件 编译器在编译整个项目的时候 需要按照依赖关系 依次编译每个源文件 比如 A cpp 依