0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

拓扑排序算法有什么作用

算法与数据结构 ? 来源:bigsai ? 作者:bigsai ? 2021-09-24 10:53 ? 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

大家好,我是bigsai。

拓扑排序,很多人都可能听说但是不了解的一种算法。不知者大多会提出这样的疑问:

这是某种排序算法?这好像是一种图论算法?图也能排序?

非线性结构在传统意义上确实不太好排序,而拓扑排序它是对有向图的顶点排成一个线性序列。并且不一定唯一。

什么是拓扑排序?

对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。

拓扑排序有何作用?

拓扑排序的应用其实还是蛮多的,拓扑排序在一些工程有多道工序时候可以获取一个有效的加工顺序、还有些游戏里的任务成就必须满足一个符合的拓扑排序才能解锁下一关、还有一些项目或者环境的依赖关系集……

当然上面的例子可能不够具体,离我们稍微近一点的就是课程学习上,比如你学习数据结构之前基本要学习C或者C++这门课,因为数据结构中需要懂和会用C++的代码;学习操作系统、计算机网络之前要学习数据结构这门课,因为里面涉及到很多数据结构和算法;学习Java Web开发前要学习JavaSE和HTML这两门课;不同院校课程安排截然不同但均能很好的连接起来,就是因为安排的课程满足一个拓扑排序。

拓扑排序还是不能理解?我举个更详细的例子,学习Java系列的教程部分,可能有下面这个顺序:

ec4e533c-11e5-11ec-8fb8-12bb97331649.png

就比如学习Java系类(部分)从Java基础,到JSP/Servlet,到SSM,到SpringBoot,SpringCloud等是个循序渐进、且有前提依赖的过程。在JSP学习要首先掌握Java基础和HTML基础。学习框架要掌握JSP/Servlet和JDBC之类才行。那么,这个学习过程即构成一个拓扑序列。当然这个序列也不唯一,你可以对不关联的学科随意选择顺序(比如Html和Java可以随便先开始哪一个)。

那上述序列可以简单表示为:

这五种均为可以选择的学习方案,对课程安排可以有参考作用,这五个都是上面有向无环图(DAG)的拓扑序列,只是小的选择的策略不同(先学Java或者先学HTML不要紧,但是要满足整个顺序要求),不影响满足规则顺序!

对于拓扑排序,还有一些比较专业的名词需要铭记:

DAG:有向无环图

AOV网:数据在顶点,顶点表示活动,边表示活动的先后关系,可以理解为一种面向对象。

AOE网:数据在边上,顶点表示事件,有向边表示活动,边上的权值表示该活动持续的时间,可以理解为面向过程。

很多人不知道AOE网干啥用的,拓扑排序是解决一个工程能否顺序进行的问题,但有时还需解决工程完成需要的最短时间。而AOE经常使用在求关键路径中(这里就先不进行详细介绍内容和算法了),图片来源https://www.cnblogs.com/svod5306/p/14723338.html)。

我们今天讲的拓扑排序就是在AOV中找到不破坏图结构的序列,对于有向无环图,需要注意一下图中:若A在B前面,则不存在B在A前面的路径(不能成环)。图中两个相邻节点在拓扑序列中只需要满足前后关系而不一定需要相邻(节点只需满足相对的前后关系,所以拓扑排序并不一定唯一)。

算法分析上面简单的介绍了拓扑排序,下面详细讲讲拓扑排序的求法。

正常步骤为(方法不一定唯一):

1.从DAG图中找到一个没有前驱的顶点输出。可以遍历入度为0的节点,也可以用优先队列维护。

2.删除以这个点为起点的边。删除一条边,其指向节点的入度减1,这样为了找到下个没有前驱节点的顶点。

3.重复上述,直到最后一个顶点被输出。如果还有顶点未被输出,则说明有环!

对于上图的简单序列,可以简单描述步骤为:

step1:删除节点1(或者2)及其指向的边,将节点输出

step2:删除节点2(或者3)及其指向的边,将节点输出

step2(这里进行两步):删除节点3(或者4)及其指向的边,将节点输出,紧接着删除节点3(或者6)其指向的边,将节点输出。

step3:按照上述规则重复进行,直到所有节点都被删除。

这样就完成一次拓扑排序流程,得到一个拓扑序列,但是这个序列并不唯一,从算法执行过程中也看到有很多选择方案,具体得到结果看你算法的设计了,但只要满足DAG图中前后相对关系。

另外观察 1 2 4 3 6 5 7 8 这个序列满足我们所说的有关系的节点指向的在前面,被指向的在后面。如果完全没关系那不一定前后(例如1,2)

代码实现对于拓扑排序,如何用代码实现呢?

虽然在上面详细介绍了思路和流程,也很通俗易懂,但是实际上代码的实现还是很需要斟酌的,如何在空间和时间上能够得到较好的平衡且取得较好的效率?

首先要考虑存储,对于节点,是用邻接矩阵还是邻接表存储呢,通常拓扑排序如果使用矩阵存储都是比较稀疏的,比较浪费内存空间,这里还是使用邻接表来存储节点。

另外,如果图中节点是1,2,3,4,5,6这样的有序编号,我们可以直接用数组,但是如果遇到1,2,88,9999类似不连续跨度很大编号节点,也可以考虑用Map存储映射一下位置。

那么我们具体的代码思想为:

①新建node类,包含节点数值和它的指向节点集合(这里直接用List集合)

②初始化一个人node数组,输入/枚举节点之间关系,被指向的节点入度+1!(例如A—》C)那么C的入度+1;

③扫描所有node(这里扫描数组)。将所有入度为0的点加入一个容器栈(队列)中。

④当栈(队列)不空的时候,抛出其中任意一个node(只要入度为零可以随便选择顺序)。将node输出,并且node指向的所有节点入度减1。如果某个点的入度被减为0,那么就将它加入栈(队列)。

⑤重复上述操作,直到栈(队列)为空。

这里主要是利用栈或者队列储存入度只为0的节点,只需要初次扫描表将入度为0的放入栈(队列)中。

因为节点之间是有相关性的,一个节点若想入度为零,那么它的前驱节点点肯定在它前入度为0,拆除关联箭头将自己入度减1,在一个有向无环图中总会有大于等于1个入度为0的节点。

在具体实现上,方式是有多样的,我的这个只是一个简单的演示,效率不一定很高,大家参考一下即可。

具体实现代码为:

import java.util.ArrayDeque;

import java.util.ArrayList;

import java.util.List;

import java.util.Queue;

import java.util.Stack;

public class tuopu {

static class node

{

int value;

List《Integer》 next;

public node(int value) {

this.value=value;

next=new ArrayList《Integer》();

}

public void setnext(List《Integer》list) {

this.next=list;

}

}

public static void main(String[] args) {

// TODO Auto-generated method stub

node []nodes=new node[9];//储存节点

int a[]=new int[9];//储存入度

List《Integer》list[]=new ArrayList[10];//临时空间,为了存储指向的集合

for(int i=1;i《9;i++)

{

nodes[i]=new node(i);

list[i]=new ArrayList《Integer》();

}

initmap(nodes,list,a);

//主要流程

//Queue《node》q1=new ArrayDeque《node》();

Stack《node》s1=new Stack《node》();

for(int i=1;i《9;i++)

{

//System.out.print(nodes[i].next.size()+“ 55 ”);

//System.out.println(a[i]);

if(a[i]==0) {s1.add(nodes[i]);}

}

while(!s1.isEmpty())

{

node n1=s1.pop();//抛出输出

System.out.print(n1.value+“ ”);

List《Integer》next=n1.next;

for(int i=0;i《next.size();i++)

{

a[next.get(i)]--;//入度减一

if(a[next.get(i)]==0)//如果入度为0

{

s1.add(nodes[next.get(i)]);

}

}

}

}

private static void initmap(node[] nodes, List《Integer》[] list, int[] a) {

list[1].add(3);

nodes[1].setnext(list[1]);

a[3]++;

list[2].add(4);list[2].add(6);

nodes[2].setnext(list[2]);

a[4]++;a[6]++;

list[3].add(5);

nodes[3].setnext(list[3]);

a[5]++;

list[4].add(5);list[4].add(6);

nodes[4].setnext(list[4]);

a[5]++;a[6]++;

list[5].add(7);

nodes[5].setnext(list[5]);

a[7]++;

list[6].add(8);

nodes[6].setnext(list[6]);

a[8]++;

list[7].add(8);

nodes[7].setnext(list[7]);

a[8]++;

}

}

输出结果

2 4 6 1 3 5 7 8

当然,上面说过用栈和队列都可以!如果使用队列就会得到1 2 3 4 5 6 7 8 的拓扑序列

至于图的构造,因为没有条件可能效率并不高,算法也可能不太完美,如有优化错误还请大佬指正!

拓扑排序找环前面说到,拓扑排序需要在有向无环图中才能得到一个拓扑序列,但是如果给定一个有向图,怎么知道它是否可以形成一个拓扑序列呢?

当然是在拓扑排序算法上进行改动,我们在进行拓扑排序会删除所有入度为0的节点,但是如有有环那么删除节点个数就小于所有节点个数,在具体实现上,我们只需要在栈或者队列抛出时候通过一个计数器统计数字即可。

当然这个问题力扣207有原题可以看看自己代码是否能够ac,问题描述:

你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。

在选修某些课程之前需要一些先修课程。先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。

例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。

请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。

分析上面已经给出,不过在具体实现代码的时候比较灵活,不一定非得创建node类,思路上理的清即可。

实现代码:

class Solution {

public boolean canFinish(int numCourses, int[][] prerequisites) {

int indegree[]=new int[numCourses];

List《Integer》 next[]=new ArrayList[numCourses];

for(int i=0;i《numCourses;i++){

next[i]=new ArrayList();

}

for(int i=0;i《prerequisites.length;i++) {

int preid=prerequisites[i][1];

int courseid=prerequisites[i][0];

indegree[courseid]++;//入度加一

next[preid].add(courseid);//next指向

}

Queue《Integer》queue=new ArrayDeque《》();

for(int i=0;i《numCourses;i++) {//加入入度为0的节点

if(indegree[i]==0){

queue.add(i);

}

}

int nodeNum=0;//判断删除节点数量 入度为0删除 如果删除所有那么返回true

while (!queue.isEmpty())

{

nodeNum++;

int nodeId=queue.poll();

for(int i=0;i《next[nodeId].size();i++)

{

int nodeIndex=next[nodeId].get(i);

indegree[nodeIndex]--;

if(indegree[nodeIndex]==0) {

queue.add(nodeIndex);

}

}

}

if(nodeNum==numCourses)

return true;

return false;

}

}

好了,到这里拓扑排序内容讲解完毕!

责任编辑:haq

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • JAVA
    +关注

    关注

    20

    文章

    2989

    浏览量

    110837
  • 拓扑结构
    +关注

    关注

    6

    文章

    328

    浏览量

    40201
  • 代码
    +关注

    关注

    30

    文章

    4906

    浏览量

    71050

原文标题:排个课表学会了拓扑排序!有点意思

文章出处:【微信号:TheAlgorithm,微信公众号:算法与数据结构】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    低成本电源排序器解决方案

    绝大多数负载点DC-DC转换器可以将上一个转换器的电源就绪输出连接至下一个转换器的使能输入,实现上电排序。这种方法只适合比较简单的设计,不能满足多数现代微处理器和DSP的要求一这类器件要求断电顺序必须与上电顺序相反。许多厂商针对这类应用推出了可编程排序IC,但器件价格较为
    的头像 发表于 05-21 09:55 ?599次阅读
    低成本电源<b class='flag-5'>排序</b>器解决方案

    开关电源功率变换器拓扑与设计

    详细讲解开关电源功率变换器的各种拓扑电路,通过实例详细讲解。 共分为12章,包括功率变换器的主要拓扑介绍和工程设计指南两大部分内容。其中,拓扑部分主要包括正激、反激、对称驱动桥式、隔离Boost
    发表于 05-19 16:26

    工业相机在焊缝跟踪中的关键作用哪些

    与形态,帮助系统动态调整焊枪轨迹,实现高精度、高效率的自动化焊接,今天一起了解工业相机在焊缝跟踪中的关键作用哪些。 焊接挑战与视觉需求 在传统焊接中,工件定位误差、夹具公差和热变形等因素易导致焊缝偏移,而
    的头像 发表于 05-13 17:56 ?235次阅读
    工业相机在焊缝跟踪中的关键<b class='flag-5'>作用</b><b class='flag-5'>有</b>哪些

    开关电源拓扑结构介绍

    一 、绪论开关电源电路拓扑是指功率器件和电磁元件连接在电路中的方式,而磁性元件设计、闭环补偿电路以及所有其他电路元件的设计都依赖于拓扑拓扑可分为:开关型和非开关型两大类。其中开关型拓扑
    发表于 05-12 16:04

    智慧路灯哪些功能和作用

    智慧路灯哪些功能和作用 智慧灯杆屏
    的头像 发表于 03-20 17:00 ?557次阅读
    智慧路灯<b class='flag-5'>有</b>哪些功能和<b class='flag-5'>作用</b>

    详解Linux sort命令之掌握排序技巧与实用案例

    在linux系统使用过程中,提供了sort排序命令,支持常用的排序功能。 常用参数 sort命令支持很多参数,常用参数如下: ? 短参数 长参数 说明 -n – number-sort 按字符串数值
    的头像 发表于 01-09 10:10 ?1019次阅读

    TimSort:一个在标准函数库中广泛使用的排序算法

    在计算机科学的领域,排序算法是每位学生必学的基础,而排序的需求是每位程序员在编程过程中都会遇到的。 在你轻松调用 .sort() 方法对数据进行排序时,是否曾好奇过,这个简单的方法背后
    的头像 发表于 01-03 11:42 ?636次阅读

    MOSFET栅极和源极的下拉电阻什么作用

    MOSFET栅极与源极之间加一个电阻?这个电阻什么作用?
    的头像 发表于 12-26 14:01 ?4627次阅读
    MOSFET栅极和源极的下拉电阻<b class='flag-5'>有</b>什么<b class='flag-5'>作用</b>

    DHB拓扑出现输出电压噪声怎么解决

    本人小白,在设计DHB拓扑的时候,输出电压波形遇到了如图所示的波形,输出电压为100V,现在输出电压波形好像开关频率的噪声,使用单移相调制,发生在开关管关断的时候,这个问题要怎么解决
    发表于 12-21 11:47

    【「从算法到电路—数字芯片算法的电路实现」阅读体验】+内容简介

    。本书力求从算法、芯片设计、软件开发等多个角度解读基础算法电路的设计,涵盖了溢出保护、符号运算、浮点运算、位宽确定等运算电路基础知识,以及除法器、信号发生器、滤波器、小数分频器等常用基本算法
    发表于 11-21 17:14

    电源拓扑快速参考指南

    电子发烧友网站提供《电源拓扑快速参考指南.pdf》资料免费下载
    发表于 11-13 15:25 ?7次下载
    电源<b class='flag-5'>拓扑</b>快速参考指南

    请问GDE中的NR算法反应慢怎么解决?

    我在使用NR(NoiseReduction)算法时发现算法作用的时间太长,输入1K正弦波测试,大约是在输入40秒以后出现下图转变 再过段时间又变成下图的样子。 但是播放器重新开始的短暂停止也
    发表于 10-29 07:42

    时间复杂度为 O(n^2) 的排序算法

    作者:京东保险 王奕龙 对于小规模数据,我们可以选用时间复杂度为 O(n2) 的排序算法。因为时间复杂度并不代表实际代码的执行时间,它省去了低阶、系数和常数,仅代表的增长趋势,所以在小规模数据情况下
    的头像 发表于 10-19 16:31 ?1796次阅读
    时间复杂度为 O(n^2) 的<b class='flag-5'>排序</b><b class='flag-5'>算法</b>

    TPS54120排序和跟踪

    电子发烧友网站提供《TPS54120排序和跟踪.pdf》资料免费下载
    发表于 10-10 10:54 ?0次下载
    TPS54120<b class='flag-5'>排序</b>和跟踪

    常用的ADC滤波算法哪些

    ADC(模数转换器)滤波算法在信号处理中起着至关重要的作用,它们能够帮助我们提取出有用的信号,同时滤除噪声和干扰。以下是常用的ADC滤波算法详解,这些算法各具特色,适用于不同的应用场景
    的头像 发表于 10-08 14:35 ?1231次阅读