数据结构:图的拓扑排序与关键路径

目录

一、拓扑排序

1.1、算法的基本步骤

1.2、算法实现

1.4、习题思考

1.5、DFS生成逆拓扑序


一、拓扑排序

43dff1fbc5604feeaecc9992b4c901c4.png

        AOV网:有向图中, 顶点表示活动(或任务), 有向边表示活动(或任务)间的先后关系,称这样的有向图为AOV网(Activity On Vertex Network)。
        AOV网络中不能出现有向回路,即有向环。
         拓扑序列:就是把AOV网中的所有顶点排成一个线性序列,若AOV网中存在有向边 Vi Vj ,则在该序列中, Vi 必位于 Vj 之前。在拓扑序列中,先进行的任务一定在后进行的任务的前面。按照拓扑序列完成各子任务,就可以顺利完成整个任务。拓扑序列未必唯一。
如果不能排成一个拓扑序列,则说明AOV网络中存在有向环。

1.1、算法的基本步骤

①从图中选择一个入度为0的顶点并输出。

②从图中删除该顶点及该顶点引出的所有边。

③执行①②,直至所有顶点已输出,或剩余顶点入度均不为0(说明存在环,无法继续拓扑排序)

时间复杂度O(n+e):计算入度O(n+e),删除顶点O(n+e)。

1.2、算法实现

        实际上我们并不需要真正的删除该顶点,我们只需要记录每个顶点的入度,在“删除”某个顶点之后,它所邻接到的所有顶点的入度都减1即可。并且我们用栈保存入度为0的顶点,以供删除。课本上用数组实现栈,这里直接采用STL:stack。

#include<bits/stdc++.h>
using namespace std;
#define n 10
struct Edge{
    int VerName;
    int cost;
    Edge * next;
};
struct Vertex{
    int VerName;
    Edge * edge=nullptr;
};
vector<int> cnt(n);
Vertex Head[n];
vector<int> path;
void TopoOrder(){//进行拓扑排序,并判断是否存在环路
    path.clear();
    cnt.assign(n,0);
    for(auto & i : Head){//计算入度
        for(Edge * edge=i.edge;edge!=nullptr;edge=edge->next){
            cnt[edge->VerName]+=1;//入度++
        }
    }
    stack<int> sta;
    for(int i=0;i<n;++i) if(!cnt[i]) sta.push(i);//初始化入度为0的家伙
    int num=0;
    while(!sta.empty()){//拓扑排序
        int cur=sta.top();sta.pop();
        path.emplace_back(cur);//路径存入
        for(Edge * edge=Head[cur].edge;edge!=nullptr;edge=edge->next){//删除点
            cnt[edge->VerName]-=1;//入度++
            if(cnt[edge->VerName]==0) sta.push(edge->VerName);
        }
    }

    if(path.size()!=n) printf("有向图中存在环路");
    else for(auto i:path) printf("%d ",i);
    return;
}

int main(void){
    return 0;
}

        由于一个顶点的入度被删减为0时,不可能再有边指向该顶点,因此该顶点不会被再次访问,则其cnt[i]空闲了。即当cnt[i]==0时,cnt[i]就不会被访问了,i也会入栈,因此我们可以利用这个空闲直接在cnt上实现栈。

优化用cnt数组空闲空间实现栈(静态链表):

int top=-1;//模拟栈顶
for(int i=0;i<n;++i){
    if(cnt[i]==0){
        cnt[i]=top;//指向以前的栈顶
        top=i;//栈顶变为i
    }
}

while(top!=-1){
    int cur=top;
    path.emplace_back(cur);
    top=cnt[top];//静态链表,相当于弹栈
    for(Edge * edge=Head[cur].edge;edge!=nullptr;edge=edge->next){//删除点
        cnt[edge->VerName]-=1;//入度++
        if(cnt[edge->VerName]==0) {
            cnt[edge->VerName]=top;
            top=edge->VerName;
        }
    }
}

1.4、习题思考

(1)给定一个图和顶点序列,编写算法判断该序列是否是图的拓扑序列。

只需要先计算一次入度,然后扫描所给定的顶点序列,对于每一个顶点:

①先判断该顶点入度是否为0,如果不为0,则说明不是拓扑序,如果为0,则进行②

②将该顶点的边删除(即为其邻接顶点减少入度),之后继续扫描。

        实际上我们假定给定的顶点序列是拓扑序列,则按照该顶点序列选择顶点删除边,选择的顶点一定是入度为0的。

(2)802. 找到最终的安全状态

60caf4bc64614950b536e8b55f93d644.png

        如果本题正着想,则从一个点开始DFS遍历,如果它能遍历到终端结点,则路径上的所有点都能遍历到终端结点,如果它的所有邻接结点都是安全结点,则它也将是安全结点,如果它有一个邻接结点不是安全结点则必然它也不是安全结点(换句话说如果存在环路,则遍历到一个结点不是安全结点且已经被遍历过,则这条路并不通往终端节点,则它不是安全结点)。然后继续寻找下一个不是安全结点的结点且未被遍历过的结点开始遍历。<不是安全结点的结点且被遍历过的结点一定不可能是安全结点了,因为如果它是,那么它的所有邻接结点都是安全结点。后根DFS也将判断它为安全结点。>


        如果说你能走到终端结点,你才算是安全结点。那么反过来走,只能被终端节点才能走到的结点才是安全结点。考察反向图,终端节点是入度为0的顶点,进行拓扑排序,排序过程中如果入度为0则被认为是安全结点(证明:开始时终端结点为安全结点,入度为0,删除其所有出边,剩下的结点入度为0的则说明它们只能被终端结点走到,它们是安全结点,然后把它们当作终端结点继续执行同样的操作,以此类推)。

class Solution {
public:
    vector<int> eventualSafeNodes(vector<vector<int>>& graph) {
        int n=graph.size();
        vector<vector<int>> gra(n,vector<int>{});
        vector<int> cnt(n);
        stack<int> sta;
        vector<int> ans;
        for(int i=0;i<n;++i){
            cnt.at(i)=graph.at(i).size();
            if(!cnt.at(i)){
                sta.push(i);
            }
            for(int j=0;j<cnt.at(i);++j){
                gra.at(graph.at(i).at(j)).emplace_back(i);
            }
        }
        while(!sta.empty()){
            ans.push_back(sta.top());sta.pop();
            for(auto i:gra[ans.back()]){
                cnt.at(i)--;
                if(cnt.at(i)==0) sta.push(i);
            }
        }
        sort(ans.begin(),ans.end());
        return ans;
    }
};
//什么STL大王,在这全程at(),看得脑袋晕,一般还是别用了(。。),用起来自己看不明白呀,除非你想测试越界

1.5、DFS生成逆拓扑序

为什么DFS能生成逆拓扑序?

        我们来思考一下拓扑序列的原始定义。在一个拓扑序列中,如果vi在vj之前,则必然在有向图中有vi→vj。那么如果我们使用DFS“后根遍历”,则对于任意的vi→vj,一定有vi在vj之后输出,换句话说,对于有向图中的每一条边vi→vj均满足,vi在vj之后,如果将这一输出序列反转,则等价于对于有向图中的每一条边vi→vj均满足,vi在vj之前。相当于对于任何一个顶点vi,其后继邻接顶点都会在其之前被输出,否则它不会被输出。反过来就是,只有当vi被输出之后,其邻接顶点才会被输出,即满足拓扑排序。因此DFS“后根输出”为拓扑排序的逆序。

        当然你得先保证这是一个AOV网络,不然DFS就变成了纯遍历了。

int vis[n];
void DFS_TopoOrder(int root){
    vis[root]=1;
    for(Edge * edge=Head[root].edge;edge!=nullptr;edge=edge->next){
        if(vis[root]==0)
            DFS_TopoOrder(edge->VerName);
    }
    printf("%d ",root);
    return;
}
for(int i=0;i<n;++i)
    if(cnt[i]==0) DFS_TopoOrder(i);//从入度为0的开始遍历

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

在基于全志V851se的TinyVision上手动构建 Linux 6.1 + Debian 12 镜像

构建 SyterKit 作为 Bootloader SyterKit 是一个纯裸机框架&#xff0c;用于 TinyVision 或者其他 v851se/v851s/v851s3/v853 等芯片的开发板&#xff0c;SyterKit 使用 CMake 作为构建系统构建&#xff0c;支持多种应用与多种外设驱动。同时 SyterKit 也具有启动引导的功能&a…

Kotlin: 协程的四种启动模式(CoroutineStart)

点击查看CoroutineStart英文文档 创建协程的三种方式 runBlocking 运行一个协程并且会阻塞当前线程&#xff0c;直到它完成。launch 启动一个新的协程&#xff0c;不会阻塞当前线程&#xff0c;并且返回一个Job&#xff0c;可以取消。async async和await是两个函数&#xff0c…

【UE 插件】UE4 虚幻引擎 插件开发(带源码插件打包、无源码插件打包) 有这一篇文章就够了!!!

目录 0 引言1 快速入门1.1 新建插件的前提1.2 创建插件步骤1.3 打包插件 2 无源代码的插件制作3 插件详细介绍3.1 插件的使用方法3.1 UE 预置插件模版3.1.1 空白3.1.2 纯内容3.1.3 编辑器独立窗口3.1.4 编辑器工具栏按钮3.1.5 编辑器模式3.1.6 第三方库3.1.7 蓝图库 3.2 插件中…

【视频异常检测】Delving into CLIP latent space for Video Anomaly Recognition 论文阅读

Delving into CLIP latent space for Video Anomaly Recognition 论文阅读 ABSTRACT1. Introduction2. Related Works3. Proposed approach3.1. Selector model3.2. Temporal Model3.3. Predictions Aggregation3.4. Training 4. Experiments4.1. Experiment Setup4.2. Evaluat…

Re62:读论文 GPT-2 Language Models are Unsupervised Multitask Learners

诸神缄默不语-个人CSDN博文目录 诸神缄默不语的论文阅读笔记和分类 论文全名&#xff1a;Language Models are Unsupervised Multitask Learners 论文下载地址&#xff1a;https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learner…

jar读取目录配置、打包jar后无法获取目录下的配置

jar读取目录配置、打包jar后无法获取目录下的配置 jar读取目录配置、打包jar后无法获取目录下的配置。java打成jar包后获取不到配置文件路径。解决项目打成jar包上线无法读取配置文件。打包jar后无法读取resource下的配置文件 场景 需要读取 src/main/resources/mapper下的所…

计算机网络:TCP篇

计网tcp部分面试总结 tcp报文格式&#xff1a; 序列号&#xff1a;通过SYN传给接收端&#xff0c;当SYN为1&#xff0c;表示请求建立连接&#xff0c;且设置序列号初值&#xff0c;后面没法送一次数据&#xff0c;就累加数据大小&#xff0c;保证包有序。 确认应答号&#x…

算法——贪心算法

《算法图解》——贪心算法 # 首先创建一个表&#xff0c;包含所覆盖的州 states_needed set([mt,wa,or,id,nv,ut,az]) # 传入一个数组&#xff0c;转换成一个集合#可供选择的广播台清单 stations {} stations[kone] set([id,nv,ut]) #用集合表示想要覆盖的州&#xff0c;且不…

【IC设计】Verilog线性序列机点灯案例(四)(小梅哥课程)

文章目录 该系列目录&#xff1a;设计环境设计目标设计思路RTL及Testbench代码RTL代码Testbenchxdc约束 仿真结果 声明&#xff1a;案例和代码来自小梅哥课程&#xff0c;本人仅对知识点做做笔记&#xff0c;如有学习需要请支持官方正版。 该系列目录&#xff1a; Verilog线性…

ChatGPT是什么,怎么使用,需要注意些什么?

一、ChatGPT 是什么&#xff1f; ChatGPT&#xff0c;全称聊天生成预训练转换器&#xff08;Chat Generative Pre-trained Transformer&#xff09;&#xff0c;是 OpenAI 开发的人工智能(AI)聊天机器人程序&#xff0c;于2022年11月推出。该程序使用基于GPT-3.5、GPT-4架构的…

编写一个sum函数,由用户输入参数n,并计算1到n的整数和--c语言

#include<stdio.h> int sum(int n); int sum(int n) {int result0;do{resultresultn;//先从n开始加}while(n-->0);return result; }int main() {int n,result;printf("请输入参数");scanf("%d",&n);printf("结果是&#xff1a;%d",…

【腾讯云 TDSQL-C Serverless 产品体验】TDSQL-C MySQL Serverless实践之路

【腾讯云 TDSQL-C Serverless 产品体验】TDSQL-C MySQL Serverless实践之路 腾讯云TDSQL-C联合CSDN推出了一款云数据库产品测评活动&#xff0c;让我们一起来体验一下。 一、什么是云数据库&#xff1f; 云数据库是指被优化或部署到一个虚拟计算环境中的数据库&#xff0c;可以…

数码管的动态显示(四)

1.原理 2.代码 2.1 seg_595_dynamic.v module seg_595_dynamic(input wire sys_clk ,input wire sys_rst_n ,input wire[19:0] data ,input wire[5:0] point ,input wire sign ,input wire seg_en ,output wire ds ,output wire oe…

.htaccess全站设置SSL,wordpress全站设置SSL,网站重定向的次数过多”错误最佳解决方法教程

.htaccess全站设置SSL,wordpress全站设置SSL&#xff0c;网站重定向的次数过多”错误最佳解决方法教程 网上找了很多教程网无效**.htacces**设置&#xff0c;访问后台出现重定向次数过多&#xff0c;导致无法访问 找了好久&#xff0c;测试用AI机器人无法解决&#xff0c;参考…

基于PyTorch的视频分类实战

1、数据集下载 官方链接&#xff1a;https://serre-lab.clps.brown.edu/resource/hmdb-a-large-human-motion-database/#Downloads 百度网盘连接&#xff1a; https://pan.baidu.com/s/1sSn--u_oLvTDjH-BgOAv_Q?pwdxsri 提取码: xsri 官方链接有详细的数据集介绍&#xf…

ASP .Net Core 8.0 依赖注入的三种注入模式

&#x1f433;前言 &#x1f340;在.NET中&#xff0c;依赖注入&#xff08;Dependency Injection&#xff0c;简称DI&#xff09;是一种设计模式&#xff0c;用于解耦组件之间的依赖关系。 依赖注入的核心思想是将对象的依赖关系&#xff08;即对象所需的其他服务或组件&#…

【系统架构设计师】软件架构设计 02

系统架构设计师 - 系列文章目录 01 系统工程与信息系统基础 02 软件架构设计 文章目录 系统架构设计师 - 系列文章目录 文章目录 前言 一、软件架构的概念 ★★★ 二、基于架构的软件开发 ★★★★ 三、软件架构风格 ★★★★ 1.数据流风格 2.调用/返回风格 3.独立构件风格…

鸿蒙Harmony应用开发—ArkTS声明式开发(绘制组件:Polygon)

多边形绘制组件。 说明&#xff1a; 该组件从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 无 接口 Polygon(value?: {width?: string | number, height?: string | number}) 从API version 9开始&#xff0…

面试经典150题【81-90】

文章目录 面试经典150题【81-90】530.二叉搜索树的最小绝对差值230.二叉搜索树中第k小的元素98.验证二叉搜索树92.反转链表II25.K个一组翻转链表146.LRU缓存909. 蛇梯棋&#xff08;未做&#xff09;433.最小基因变化127.单词接龙&#xff08;未做&#xff09;17.电话号码的字母…

C++Qt学习——QFile、QPainter、QChart

目录 1、QFile&#xff08;文本读写&#xff09;——概念 1.1、拖入三个控件&#xff0c;对pushButton进行水平布局&#xff0c;之后整体做垂直布局 1.2、按住控件&#xff0c;转到槽&#xff0c;写函数 1.3、打开文件控件 A、首先引入以下两个头文件 B、设置点击打开文件控…