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;//输出顶点i
		for (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的出度减1
				if (!outdegree[j])Sta[++top] = j;//如果顶点j的出度为0则入栈
			}
	}
	if (count < n)return false;//有环
	else return true;
}

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


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

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

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

相关文章

深圳晶彩智能JC3636W518C开箱实现电脑副屏功能

深圳晶彩智能发布了JC3636W518C 这是一款中国制造的&#xff0c;铝合金外壳&#xff0c;价格非常震撼的开发板。原创是billbill的up播主萨纳兰的黄昏设计的ESP32太极小派&#xff0c;由深圳晶彩智能批量生产。 该款 LCD 模块采用 ESP32-S3R8 芯片作为主控,该主控是双核 MCU&…

Vulnhub:DC-1

1.环境搭建 靶机下载地址 将下载的靶机导入到Oracle VM VirtualBox中&#xff0c;设置仅主机模式&#xff0c;使用和kali相同的网卡 2.渗透过程 使用nmap工具进行主机发现扫描 nmap -sn 192.168.56.0/24 发现靶机ip地址&#xff0c;使用nmap工具进行靶机端口扫描 nmap -sS…

一文说透Springboot单元测试

你好&#xff0c;我是柳岸花开。 一、单元测试说明 1 单元测试的优点与基本原则 一个好的单元测试应该具备以下FIRST 原则和AIR原则中的任何一条&#xff1a; 单元测试的FIRST 规则 Fast 快速原则&#xff0c;测试的速度要比较快&#xff0c; Independent 独立原则&#xff0c;…

Qt 多窗体、复用窗口的使用

1.继承自QWidge的窗口的呈现&#xff0c;作为tabPage呈现&#xff0c;作为独立窗口呈现 2.继承自QMainWindow的窗口的呈现&#xff0c;作为abPage呈现&#xff0c;作为独立窗口呈现 1. 继承自QWidge的窗口的呈现 1.1 作为tabPage呈现 void MutiWindowExample::on_actWidgetI…

AI绘画入门实践|Midjourney 提示词的使用技巧

提示词长短 尽可能做到简洁明了。 提示词很短 MJ 出图的随机性更高&#xff0c;创造的内容更有想象力&#xff0c;更适合创意发散的图像生成。 a dog 提示词很长 MJ 出图会更加精准&#xff0c;但描述太过详细&#xff0c;有可能出现AI理解不到位的情况。 越到后面的提示词&…

风险评估:IIS的安全配置,IIS安全基线检查加固

「作者简介」&#xff1a;冬奥会网络安全中国代表队&#xff0c;CSDN Top100&#xff0c;就职奇安信多年&#xff0c;以实战工作为基础著作 《网络安全自学教程》&#xff0c;适合基础薄弱的同学系统化的学习网络安全&#xff0c;用最短的时间掌握最核心的技术。 这一章节我们需…

Java面试八股之Redis集群Cluster

Redis集群Cluster Redis Cluster是一种基于数据分片&#xff08;Sharding&#xff09;的分布式缓存和存储系统&#xff0c;它实现了数据的水平扩展、高可用性和自动故障转移。以下是对Redis Cluster模式详细实现流程的描述&#xff1a; 1. 初始化与配置 部署节点&#xff1a…

flutter 手写 TabBar

前言&#xff1a; 这几天在使用 flutter TabBar 的时候 我们的设计给我提了一个需求&#xff1a; 如下 Tabbar 第一个元素 左对齐&#xff0c;试了下TabBar 的配置&#xff0c;无法实现这个需求&#xff0c;他的 配置是针对所有元素的。而且 这个 TabBar 下面的 滑块在移动的时…

产品经理-产品经理会在项目中遇到的几个问题(16)

项目中遇到了需求变更怎么办&#xff1f; 首先要弄清楚需求变更的原因是什么。如果是因为在迭代的过程中更好地理解了用户需求 进而产生了更好的需求则完全是正常的。如果是因为老板的需求 那就需要和老板沟通清楚&#xff0c;并且确保自己能理解老板的需求&#xff0c;而且这个…

软件测试——测试用例

工作职责&#xff1a; 1.负责产品系统测试&#xff0c;包括功能测试、性能测试、稳定性测试、用户场景测试、可靠性测试等。 2.负责测试相关文档的编写&#xff0c;包括测试计划、测试用例、测试报告等。 3.负责自动化测试框架、用例的维护。 岗位要求&#xff1a; 1.熟练…

800块,我从淘宝上买AGV……

导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。 新书《智能物流系统构成与技术实践》人俱乐部 从淘宝上打算够购买一台AGV小车&#xff0c;上去一搜&#xff0c;嘿&#xff0c;你别说&#xff0c;还真有。便宜的才200块钱。 很兴奋把…

17-8 向量数据库之野望8 - 7 个主流向量数据库

​​​​​​ 在快速发展的人工智能 (AI)、机器学习 (ML) 和数据工程领域,对高效数据存储和检索系统的需求至关重要。矢量数据库已成为管理这些技术通常依赖的复杂高维数据的关键解决方案。在这里,我们探讨了每个 AI/ML/数据工程师都应该熟悉的七个矢量数据库,重点介绍了它们…

【Linux】01.Linux 的常见指令

1. ls 指令 语法&#xff1a;ls [选项] [目录名或文件名] 功能&#xff1a;对于目录&#xff0c;该命令列出该目录下的所有子目录与文件。对于文件&#xff0c;将列出文件名以及其他信息 常用选项&#xff1a; -a&#xff1a;列出当前目录下的所有文件&#xff0c;包含隐藏文件…

JavaSE学习笔记第三弹之异常抛出

今天我们继续来学习JavaSE相关的知识&#xff0c;希望与大家共同努力。 目录 异常 什么是异常 运行时异常 编译时异常 ​编辑 为什么需要异常处理机制 错误 异常的处理与抛出 异常处理 异常抛出 自定义异常 结语 异常 什么是异常 Java中异常是一种在程序运行时发…

Java二十三种设计模式-原型模式(5/23)

Java中的原型模式&#xff1a;深入解析与应用实践 引言 原型模式&#xff08;Prototype Pattern&#xff09;是一种创建型设计模式&#xff0c;它使用一个已有的对象作为原型&#xff0c;通过复制这个原型来创建新的实例。这种模式适用于对象的创建成本较高&#xff0c;或者对…

BL201分布式I/O耦合器连接Profinet网络

钡铼技术的BL201分布式I/O耦合器是一个用于Profinet网络的设备&#xff0c;用于连接远程输入/输出&#xff08;I/O&#xff09;设备到控制系统&#xff0c;如可编程逻辑控制器&#xff08;PLC&#xff09;&#xff0c;能够实现分布式的I/O连接和通信。 它支持标准Profinet IO …

Qt实现一个简单的视频播放器

目录 1 工程配置 1.1 创建新工程 1.2 ui界面配置 1.3 .pro配置 2 代码 2.1 main.c代码 2.2 widget.c 2.3 widget.h 本文主要记述了如何使用Qt编写一个简单的视频播放器&#xff0c;整个示例采用Qt自带组件就可以完成。可以实现视频的播放和暂停等功能。 1 工程配置 1.…

都说油车不行了,外资两款经典油车降价起量,与电动车展开决战

6月份的轿车销量数据出来了&#xff0c;数据显示电动汽车并没稳赢&#xff0c;燃油车终于发力了&#xff0c;反过来围剿电动汽车了&#xff0c;显示出消费者并非说不买油车&#xff0c;而是看价格&#xff0c;价格合适&#xff0c;消费者仍然会选择油车。 6月份的数据显示之前销…

AV1 编码标准帧间预测技术概述

AV1 编码标准帧间预测 AV1&#xff08;AOMedia Video1&#xff09;是一种开源的视频编码格式&#xff0c;它在帧间预测技术上做出了显著的改进和扩展&#xff0c;以提供比现有标准更高的压缩效率和更好的视频质量。以下是AV1帧间预测技术的几个关键点&#xff1a; 参考帧扩展&a…

VGMP(VRRP组管理协议)和HRP(华为冗余技术)

1、VGMP VGMP&#xff08;VRRP Group Management Protocol&#xff09;&#xff1a;VRRP组管理协议&#xff0c;是华为开发的一种私有协议&#xff0c;主要用于实现对多个VRRP组进行统一管理的功能 概述&#xff1a;VGMP协议是在VRRP协议的基础上开发的&#xff0c;其最主要的…