408数据结构-图的应用3-有向无环图、拓扑排序 自学知识点整理

前置知识:表达式,图的遍历


有向无环图描述表达式

有向无环图:若一个有向图中不存在环,则称为有向无环图,简称 D A G DAG DAG
(图片来自王道考研408数据结构2025)图片来自王道考研408数据结构2025
由王道考研-咸鱼学长的讲解方法(视频传送门),这一类题目有一个通解,具体步骤如下:

  • S t e p 1 Step\ 1 Step 1:把各个操作数不重复地排成一排。
  • S t e p 2 Step\ 2 Step 2:标出各个运算符的生效顺序(同级别之间任意)。
  • S t e p 3 Step\ 3 Step 3:按顺序加入运算符,注意“分层”。(若某个运算符的执行要基于另一个运算符和操作数的执行结果来进行,则前一个运算符在后一个的“上一层”)
  • S t e p 4 Step\ 4 Step 4:从底层向上逐层检查,看同层的运算符是否可以“合体”。

检查完毕后,得出的表达式的 D A G DAG DAG图就是顶点数最小的。

注意:在表达式的有向无环图表示中,不可能出现重复的操作数顶点。

拓扑排序

A O V AOV AOV:若用 D A G DAG DAG图表示一个工程,其顶点表示活动,用有向边 < V i , V j > <V_i,V_j> <Vi,Vj>表示活动 V i V_i Vi必须先于活动 V j V_j Vj进行这样一种关系,则将这种有向图称为顶点表示活动的网络,简称 A O V AOV AOV。在 A O V AOV AOV网中,活动 V i V_i Vi是活动 V j V_j Vj的直接前驱, V j V_j Vj V i V_i Vi的直接后继,这种前驱和后继关系具有传递性,且任何活动 V i V_i Vi不能以它自己作为自己的前驱或后继。
拓扑排序:在图论中,由一个 D A G DAG DAG图的顶点组成的序列,当且仅当满足下列条件时,称为该图的一个拓扑排序:

  1. 每个顶点出现且仅出现一次。
  2. 若顶点 A A A在序列中排在顶点 B B B的前面,则在图中不存在从 B B B A A A的路径。
    或定义为:拓扑排序是对有向无环图的一种排序,它使得若存在一条从顶点 A A A到顶点 B B B的路径,则在排序中 B B B出现在 A A A的后面。每个 A O V AOV AOV网都有一个或多个拓扑排序序列。

简而言之,拓扑排序就是这样的一种排序:如果顶点 A A A到顶点 B B B有路, B B B A A A没有,那么 A A A B B B前面。由此可见,入度为 0 0 0的顶点在拓扑排序序列里一定是在最前面的。

对一个 A O V AOV AOV网进行拓扑排序的算法有很多,下面介绍比较常用的一种方法的步骤:
① 从 A O V AOV AOV网中选择一个没有前驱(入度为 0 0 0)的顶点并输出。
② 从网中删除该顶点和所有以它为起点的有向边。
③ 重复①和②直到当前的 A O V AOV AOV网为空或者当前网中不存在无前驱的顶点为止。后一种情况说明有向图中必然存在环。(那就不是 D A G DAG DAG图了)

下图是王道书上的实例,读者可以看看,加以理解。
(图片来自王道考研408数据结构2025)
图片来自王道考研408数据结构2025

知识点回顾:图的存储与基本操作

对图采用邻接表存储表示如下:

#define MaxVertexNum 1007
typedef struct ArcNode {int adjvex;struct ArcNode* nextarc;
}ArcNode, * ArcList;
typedef struct VNode {int data;ArcNode* firstarc;
}VNode, AdjList[MaxVertexNum];
typedef struct {AdjList vertices;int vexnum, arcnum;
}ALGraph;

预定义、预处理和基本操作函数如下:

int indegree[MaxVertexNum], print[MaxVertexNum];
//记录各点入度,记录拓扑排序序列inline void Init(ALGraph& G) {//预处理for (int i = 1; i <= G.vexnum; ++i) {ArcList h = (ArcList)malloc(sizeof(ArcList));h->adjvex = 0;h->nextarc = NULL;G.vertices[i].data = 0;G.vertices[i].firstarc = h;}memset(indegree, 0, sizeof(indegree));memset(print, 0, sizeof(print));return;
}inline void AG_Insert(ALGraph& G, int i, int j) {//头插法加入边ArcNode* p = (ArcNode*)malloc(sizeof(ArcNode));p->adjvex = j;ArcNode* head = G.vertices[i].firstarc;ArcNode* tail = G.vertices[i].firstarc->nextarc;p->nextarc = tail;head->nextarc = p;return;
}

知识点回顾:栈和队列的基本概念

拓扑排序算法的实现如下:和王道书上的不同,我用了静态数组模拟栈的做法。

inline bool Topologicalsort(ALGraph G) {int Sta[MaxVertexNum], top = 0, count = 0;//用静态数组模拟栈,top为栈顶指针,count为拓扑序列数组下表memset(Sta, 0, sizeof(Sta));int i;for (i = 1; i <= G.vexnum; ++i)if (!indegree[i])Sta[++top] = i;//将初始入度为0的顶点进栈while (top) {//当栈不空时i = Sta[top--];//栈顶元素出栈print[++count] = i;//输出顶点ifor (ArcNode* p = G.vertices[i].firstarc; p != NULL; p = p->nextarc) {//将所有i指向的顶点入度减1,并将入度减为0的顶点压入栈Sta中int v = p->adjvex;if (!v)continue;if (!(--indegree[v]))Sta[++top] = v;//度为0则入栈p->adjvex = 0;}}if (count < G.vexnum)return false;//排序失败,图中存在回路else return true;//拓扑排序成功
}

对采用不同存储方式存储的图,拓扑排序的时间复杂度也不同。采用邻接表存储时,拓扑排序的时间复杂度为 O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(V+E),采用邻接矩阵存储时,拓扑排序的时间复杂度为 O ( ∣ V ∣ 2 ) O(|V|^2) O(V2)

用拓扑排序算法处理 A O V AOV AOV网时,应注意以下问题:

  1. 入度为 0 0 0的顶点,即没有前驱活动的或前驱活动都已经完成的顶点,工程可以从这个顶点所代表的活动开始或继续。
  2. 若一个顶点有多个直接后继,则拓扑排序的结果通常不唯一;但若各个顶点已经排在一个线性有序的序列中,每个顶点有唯一的前驱后继关系,则拓扑排序的结果是唯一的。
  3. 由于 A O V AOV AOV网中各顶点的地位平等,每个顶点的编号是人为的,因此可以按拓扑排序的结果重新编号,生成的 A O V AOV AOV网的新的邻接存储矩阵,这种邻接矩阵可以是三角矩阵;但对一般的图来说,若其邻接矩阵是三角矩阵,则存在拓扑序列;反之则不一定成立。

D F S DFS DFS实现拓扑排序的思想

知识点回顾:图的遍历

对于一个有向无环图 G G G,其任意结点 u , v u,v u,v,它们之间的关系必然满足下列三种之一。

  1. u u u v v v的祖先,则在调用 D F S DFS DFS访问 u u u之前,必然已经对 v v v进行过 D F S DFS DFS访问,即 v v v D F S DFS DFS访问顺序先于 u u u。从而可考虑在 D F S DFS DFS函数中设置一个时间标记,在 D F S DFS DFS调用结束时,对各顶点即使。因此,祖先的结束时间必然大于子孙的结束时间。
  2. u u u v v v的子孙,则 v v v u u u的祖先,按1中的思路, v v v的结束时间大于 u u u的结束时间。
  3. u u u v v v没有路径关系,则 u u u v v v在拓扑序列的关系任意。

于是按结束时间从大到小排列,就可以得到一个拓扑排序序列。
代码实现如下:

int tim, finishtime[MaxVertexNum];
bool visited[MaxVertexNum];//访问标记数组inline void DFSTravere(ALGraph G) {memset(visited, false, sizeof(visited));//初始化memset(finishtime, 0, sizeof(finishtime));tim = 0;for (int i = 1; i <= G.vexnum; ++i)//从第一个顶点开始深搜if (!visited[i])DFS(G, i);return;
}inline void DFS(ALGraph G, int v) {visited[v] = true;for (ArcNode* p = G.vertices[v].firstarc->nextarc; p != NULL; p = p->nextarc) {int w = p->adjvex;//依次遍历当前顶点的邻边未访问的顶点if (!visited[w])DFS(G, w);}finishtime[v] = ++tim;//搜索深度越深,tim值越小//如果要输出逆拓扑排序序列,只需把这一行改为输出v即可return;
}

完整代码可以看我的Github:传送门

例题链接:【洛谷B3644】【模板】拓扑排序 / 家谱树 解题报告

我在这篇题解里写了三种拓扑排序的三种做法,分别是邻接表实现邻接矩阵实现邻接表 D F S DFS DFS实现,感兴趣的读者可以阅读一下。

逆拓扑排序

对一个 A O V AOV AOV网,如果采用下列步骤进行排序,则称之为逆拓扑排序
① 从 A O V AOV AOV网中选择一个没有后继(出度为 0 0 0)的顶点并输出。
② 从网中删除该顶点和所有以它为终点的有向边。
③ 重复①和②直到当前的 A O V AOV AOV网为空,或者当前网中不存在无后继的顶点为止。后一种情况说明图中存在环。

由逆拓扑排序序列的性质,易知,当图采用邻接表表示法存储时,时间复杂度较高。因为每次删除一个顶点,都需要遍历整个邻接表寻找以这个顶点为终点的边。
(扩展知识:逆邻接表——一种边表保存以顶点表中的点为终点的边的图的存储方式)
用邻接矩阵表示的代码实现如下:(我根据拓扑排序改的,不知道对不对,如有不对请斧正)

inline bool ReverTopoSort() {int Sta[N], top = 0, count = 0;//静态数组模拟栈memset(Sta, 0, sizeof(Sta));int i;for (i = 1; i <= n; ++i)if (!outdegree[i])Sta[++top] = i;//出度为0的点入栈while (top) {//当栈不为空时i = Sta[top--];//弹出栈顶元素print[++count] = i;//输出for (int j = 1; j <= n; ++j)if (A[j][i]) {//如果有一条j到i的边A[j][i] = false, outdegree[j]--;//删除之,使顶点j的出度减1if (!outdegree[j])Sta[++top] = j;//如果顶点j的出度为0则入栈}}if (count < n)return false;//有环else return true;
}

深度优先搜索( D F S DFS DFS)算法输出逆拓扑排序序列则非常简单,只需把最后一行改为直接输出 v v v即可(详见前面的代码)。但是判环操作还需加上额外的条件,感兴趣的读者可以自行思考。(我就是懒得想了


对有向无环图描述表达式,408初试一般考查手推,而拓扑排序则会考察代码相关的综合题,需要结合例题深入理解相关知识点。
以上。

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

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

相关文章

ModuleNotFoundError: No module named

python脚本执行出现这个错误&#xff0c;检查是否安装了对应的模块&#xff0c;确认已经安装&#xff0c;执行还是出错 原因是我时在c程序中启动执行的python脚本&#xff0c;c程序执行是使用了sudo权限&#xff0c;此时会报错&#xff0c;而在shell中执行python&#xff08;下…

emqx(v5.0)常见问题

emqx&#xff08;v5.0&#xff09;常见问题 1 官方常见问题解答2 EMQX 启动时日志提示 “WARNING: Default (insecure) Erlang cookie is in use.” 应该怎么办&#xff1f;3 EMQX 启动时日志提示“filed to merge schema”&#xff1f; 1 官方常见问题解答 常见问题解答 2 E…

哈希表实现的并查集:Leetcode 721. 账户合并

描述 给定一个列表 accounts&#xff0c;每个元素 accounts[i] 是一个字符串列表&#xff0c;其中第一个元素 accounts[i][0] 是 名称 (name)&#xff0c;其余元素是 emails 表示该账户的邮箱地址。 现在&#xff0c;我们想合并这些账户。如果两个账户都有一些共同的邮箱地址…

前端框架学习之 搭建vue2的环境 书写案例并分析

目录 搭建vue的环境 Hello小案例 分析案例 搭建vue的环境 官方指南假设你已经了解关于HTML CSS 和JavaScript的中级知识 如果你刚开始学习前端开发 将框架作为你的第一步可能不是最好的主意 掌握好基础知识再来吧 之前有其他框架的使用经验会有帮助 但这不是必需的 最…

python开发面试-20240715

1、python GIL锁&#xff0c;以及如何避免 1、使用多进程 multiprocesssing 2、使用C扩展 3、使用异步编程 4、使用外部库如Numpy、Panda 5、GIL优化&#xff1a;python版本升级&#xff0c;可能会进行优化 2、python 内存回收 Python 使用自动内存管理来回收不再使用的对象&am…

JDK垃圾回收机制和垃圾回收算法

查看java相关信息 java -XX:PrintCommandLineFlags -version UseParallelGC 即 Parallel Scavenge Parallel Old,再查看详细信息 内存分配策略 1. 对象优先在 Eden 分配 大多数情况下&#xff0c;对象在新生代 Eden 区分配&#xff0c;当 Eden 区空间不够时&#xff0c;发…

PX4 UM982 配合F9P Base 进行 RTK 定位

UM982是新兴的常见双天线GPS模块&#xff0c;支持双天线定向&#xff0c;RTK功能&#xff0c;PX4也引入了对其的支持&#xff0c;需要按需额外设置 官方手册号称直接用F9P做地面站&#xff0c;搭配QGC使用就能进行RTK定位 但是经过实践&#xff0c;发现这样是进不了RTK模式的…

Docker---最详细的服务部署案例

提供python服务的docker一键部署&#xff0c;示例已配置负载均衡&#xff0c;不需要的在nginx.conf和docker-compose注释相关代码即可 文件结构 1、dockerfile # 服务的dockerfile# 服务依赖的镜像 FROM python:3.7# 设置容器内服务的工作目录 WORKDIR /app# 复制当前文件夹所…

AI作画入门指南:从基础到高级的全面教程

AI作画入门指南&#xff1a;从基础到高级的全面教程 AI作画是一项融合了技术与艺术的创新领域。本指南将带你从基础到高级&#xff0c;逐步掌握AI作画的技巧&#xff0c;打造出独具个性的艺术作品。 1. 什么是AI作画&#xff1f; 定义&#xff1a;AI作画是利用人工智能技术生…

基于Rspack实现大仓应用构建提效实践|得物技术

一、实践背景 随着项目的逐步迭代&#xff0c;代码量和依赖的逐渐增长&#xff0c;应用的构建速度逐步进入缓慢期。以目前所在团队的业务应用来看&#xff08;使用webpack构建&#xff09;&#xff0c;应用整体构建耗时已经普遍偏高&#xff0c;影响日常开发测试的使用效率&am…

护网--2

实验要求&#xff1a; 1、办公区设备可以通过电信链路和移动链路上网(多对多的NAT&#xff0c;并且需要保留一个公网IP不能用来转换) 2、分公司设备可以通过总公司的移动链路和电信链路访问到Dmz区的http服务器 3、多出口环境基于带宽比例进行选路&#xff0c;但是&#xff0c;…

springboot 使用注解,对注解使用切面后,Controller调用service一直报null的问题解决。

百度后的答案是&#xff1a; springboot 注解加切面 后controller, service为null 报错问题&#xff1a;“springboot 注解加切面后controller, servise为null” 通常意味着在使用Spring Boot时&#xff0c;通过注解定义的切面成功创建了&#xff0c;但是与之相关联的Controll…

实现keepalive+Haproxyde 的高可用

需要准备五台实验机 一台客户机&#xff1a;test1 两台&#xff1a;一主一备的实验机&#xff1a;test2 test3 两台真实服务器&#xff1a;nginx1 nginx2 实验 首先在两台实验机上安装Haproxy 安装依赖环境&#xff0c;并将Haproxy的包进行解压处理 yum install -y pcre…

nodejs安装部署运行vue前端项目

文章目录 1.安装nodejs2.安装Vue CLI1.配置npm镜像源&#xff1a;2.安装Vue CLI&#xff1a;3.创建Vue项目4.启动Vue项目5.Express 1.安装nodejs Node.js 是一个免费、开源、跨平台的 JavaScript 运行时环境&#xff0c;它让开发人员能够创建服务器、Web 应用、命令行工具和脚…

【自动驾驶汽车通讯协议】UART通信详解:理解串行数据传输的基石

文章目录 0. 前言1. 同步通讯与异步通讯1.1 同步通信1.2 异步通信 2. UART的数据格式3. 工作原理3.1 波特率和比特率3.2 UART的关键特性 4. UART在自动驾驶汽车中的典型应用4.1 UART特性4.2应用示例 5. 结语 0. 前言 按照国际惯例&#xff0c;首先声明&#xff1a;本文只是我自…

MFC:文本可视化输出

文章目录 1. DrawText&#xff1a;2. TextOut&#xff1a;3. SetTextColor&#xff1a;4. SetBkColor&#xff1a;5. GetTextMetrics&#xff1a; 在MFC&#xff08;Microsoft Foundation Classes&#xff09;中&#xff0c;CDC&#xff08;设备上下文类&#xff09;提供了多种…

Barabási–Albert模型详解与Python代码示例

Barabsi–Albert模型详解与Python代码示例 模型介绍 Barabsi–Albert&#xff08;BA&#xff09;模型是一种用于模拟和分析复杂网络结构的数学模型&#xff0c;特别适用于描述那些具有“无标度”特性的网络。无标度网络是指网络中节点的连接度&#xff08;度&#xff09;分布…

xlive.dll丢失怎么办,xlive.dll文件的主要用途

xlive.dll丢失怎么办&#xff1f;目前是有很多方法可以解决这个xlive.dll丢失的问题的&#xff0c;只要你仔细的去了解xlive.dll这个文件&#xff0c;至于使用哪种方法&#xff0c;主要还是看你的实际情况&#xff0c;因为情况不同选择使用的方法也是不一样的&#xff0c;下面一…

底软驱动 | Linux虚拟内存

为了更有效的管理内存并且少出错&#xff0c;现代操作系统提供了一种对主存的抽象概念&#xff0c;叫做虚拟内存(VM)。虚拟内存提供了三个重要的能力: 1.它将主存(物理内存)看成是一个存储在磁盘上的地址空间的高速缓存&#xff0c;在主存中只保留活动区域&#xff0c;并且根据…

去除重复数字

1083. 【基础】去除重复数字 [ 刷题2路4线 ] 时间限制: 1000MS 空间限制: 16MB 结果评判: 文本对比 正确/提交: 29 (21) / 45 官方标签: 数组 普及- 题目描述 给你N个数&#xff08;n&#xff1c;&#xff1d;&#xff11;&#xff10;&#xff10;&#xff09;,每个数都在&am…