C++ 图系列之基于有向无环图的拓扑排序算法

news2024/11/25 14:38:18

1. 前言

有向无环图,字面而言,指图中不存在环(回路),意味着从任一顶点出发都不可能回到顶点本身。有向无环图也称为 DAG(Directed Acycline Graph)

有向无环图可用来描述顶点之间的依赖关系,依赖这个概念在面向对象编程中经常出现。如使用B组件时,需要先有A组件,或说B组件依赖A组件,通俗言之,有A才有B。可用如下图描述。

1.png

在面向对象编程的场景中,组件之间的依赖关系链中不能出现环。如现有 3 个组件,C依赖BB依赖于A。正确的图解应该如下图。

2.png

如果在 AC添加依赖,则会出现环,在面向对象中,称此种情况为循坏依赖,显然,会导致死锁,从而让程序崩溃。如下图所示,如果图结构是用来描述面向对象中组件之间的依赖关系,则会出现逻辑悖论:

  • 图示有C才有A、有 A才有B,有B 才有 C
  • 这里就出现了先有鸡还是先有蛋的悖论问题。因为图中出现了 A需要依赖自己的矛盾。

4.png

下文将更深入讲解有向无环图的特点及应用。

2. AOV 网

任何的逻辑结构必须是特定应用场景下的语义描述,有向无环图也仅用顶点之间不能有环的场景之下。

如下图,使用有向无环图描述面向对象中组件的依赖关系,顶点与顶点之间的边(弧)描述了顶点(组件)间的制约关系。因有特定的语义环境要求,故,图中不能出现环。

5.png

有向无环图常用于描述工程系统的内部组织结构。

现实生活中,一个工程(系统)可分解成诸多子工程(系统),且子工程之间存在彼此制约现象,即某一个子工程需要等待另一个子工程或多个子工程完成后方能进行,也可称为同步(前一个子工程的结束是后一个子工程的开始的前置条件)。

当然,也会有些子工程可不依赖任何其它子工程,可称为异步(多个子工程可以同时进行)。

称遵循这种逻辑关系而构建的有向无环图为 AOV(Activity On Vertex Network)网。

AOV网中:

  • 若从顶点 i到顶点 j 之间存在一条有向路径,称顶点 i是顶点 j的前驱。或称项点 j是顶点i的后继。
  • <i,j>是图中的弧(子工程之间的制约关系),称顶点 i是顶点j的直接前驱,顶点 j是顶点i的直接后继。

当然,在AOV网中,人们关心的有 2 点:

  • 工程能否顺利完成。即在设计分解子工程时,子工程之间的依赖顺序是否正确。
  • 整个工程完成所需要的最短时间是多少。

这也是有向无环图需要提供的核心逻辑功能。

2.1 拓扑排序

AOV网这种逻辑结构在现实生活中比比皆是,如某个课程可以分成很多章节独立的知识块,其中一些知识块必需以另外一些知识块为前置条件。

如学习JAVAjdbc知识时需要有数据库基础,同时也需对网络编程有所了解。而网络编程知识又需要有多线程知识IO流知识为前提条件。除数据库外,所有知识需要有面向对象知识为前提条件。如下图所示:

6.png

如何检查上图结构的正确性?

衡量AOV网结构正确性的标准是必须保证 AOV网中不能出现回路,否则会出现自己依赖自己的悖论。

可以使用拓扑排序算法验证 AOV网结构的合理性。

拓扑排序算法的思想:

这里的排序并不是指递增或递减式的排序,而是通过算法把有向无环图中的顶点以线性序列方式输出。如果AOV网中的所有顶点都出现在它的线性序列中,则说明此 AOV网不存在环,或说拓扑排序算法可以检查图是否有环。

一定要知道,针对于AOV网,因有可以异步的顶点,所以,拓扑排序的线性序列并不是唯一的。

对上图java知识之间的AOV图,其拓扑排序算法的流程如下:

  • AOV网中选择一个没有前驱或说入度为0的顶点。图中有面向对象数据库 2 个顶点可以选择。因拓扑排序的结果不是唯一的,出现同时多个可选顶点时,可自义优先级选择策略(如按顶点的编号顺序或字符串的字典顺序)。这里选择面向对象顶点。

7.png

  • 从图中移出面向对象顶点,且删除与此顶点有关联的

8.png

  • 继续选择入度为 0 的顶点。可供选择的有线程、IO流、数据库 3个顶点。这里选择线程,并删除与线程有关联的边。

    Tip:当同时可供选择的顶点有多个,说明,此时顶点所代表的任务之间没有互相制约关系。所以,无论先执行哪一个任务都不会影响最终结果。

9.png

  • 重复上述逻辑,直到图中所有顶点均被选择出来。如下图是拓扑排序算法的选择顺序之一。注意,顺序不是唯一的。

10.png

如果图中所有顶点都出现在线性序列中,则说明,图中知识块之间的逻辑依赖关系是健康的(没有回路),否则表示AOV网的设计有问题。

拓扑排序算法并不关心最终顶点输出的顺序,仅在意是否能让所有顶点以线性方式输出。所以,拓扑排序即可以借助于队列也可以使用栈存储入度为 0 的顶点。

2.2 拓扑排序的实现

图的关系描述可以使用邻接矩阵邻接表。本文将使用邻接矩阵的存储方式实现拓扑排序算法。

2.2.1 邻接矩阵

使用邻接矩阵存储图中顶点的关系,可以很容易查询到与某个顶点的入度出度量。

  • 以某个项点的编号为行号,与此行相交列中有值的单元格数量即为此顶点的出度数量。如下图,在AOV网中,可认为有另 2 个顶点依赖此顶点。

11.png

  • 以某个顶点的编号为列号,与此列相交行中有值的单元格数量为此顶点的入度数量。如下图,可认为编号为 4 的顶点依赖另 3 个顶点。

12.png

利用邻接矩阵的上述特点,可服务于拓扑排序中查找入度为 0 的顶点。

顶点类型:

#include <iostream>
#include <queue>
using namespace std;
/*
*顶点类型
*/
struct Vertex {
	//编号
	int vid;
	//值(数据)
	string val;
	//是否已经访问
	int isVisited;
	Vertex() {
		this->isVisited=0;
	}
	Vertex(int vid,string val) {
		this->val=val;
		this->vid=vid;
		this->isVisited=0;
	}
};

AOV网类型:除了提供基本API,主要是实现拓扑排序算法

/*
* AOV 网类型
*/
class AOVGraph {
	private:
		//存储所有顶点
		Vertex* allVertexs[10];
		//邻接矩阵形式存储顶点之间的关系
		int edges[10][10];
		//顶点编号由内部维护
		int num;
	public:
		/*
		*无参构造函数,完成初始化工作
		*/
		AOVGraph() {
			this->num=0;
			for(int i=0; i<10; i++) {
				this->allVertexs[i]=NULL;
				for(int j=0; j<10; j++)
					this->edges[i][j]=0;
			}
		}
		/*
		*查询顶点是否存在
		*/
		Vertex* findVertex(string val);
		Vertex* findVertex(int vid);
		/*
		*创建顶点且返回此顶点
		*/
		Vertex* addVertex(string val);
		/*
		*添加顶点之间的关系
		*/
		void addEdge(Vertex* from,Vertex* to,int weight=1);
		/*
		*拓扑排序算法
		*/
		void topSort();
        /*
        * 输出矩阵
        */
		void show() {
            cout<<"图中顶点之间的关系:"<<endl;
			for(int i=0; i<10; i++) {
				for(int j=0; j<10; j++) {
					cout<< this->edges[i][j]<<"\t";
				}
				cout<<endl;
			}
		}
};

AOV类中函数功能介绍:

  • findVertex函数:可以按顶点的值或编号查找。
/*
* 功能:查询顶点是否存在
* 存在: 返回此顶点
* 不存在:返回 NULL
*/
Vertex* AOVGraph::findVertex(string val) {
	for(int i=0; i<this->num; i++) {
		if(this->allVertexs[i]==NULL)continue;
		if(this->allVertexs[i]->val.compare(val)==0  ) {
			return this->allVertexs[i];
		}
	}
	return NULL;
}
/*
*根据编号查找顶点
*/
Vertex* AOVGraph::findVertex(int vid) {
	for(int i=0; i<this->num; i++) {
		if(this->allVertexs[i]==NULL)continue;
		if(this->allVertexs[i]->vid==vid) {
			return this->allVertexs[i];
		}
	}
	return NULL;
}
  • addVertex函数:创建新顶点。
/*
*创建顶点且返回此顶点
*/
Vertex*  AOVGraph::addVertex(string val) {
	//先查询
	Vertex* ver= this->findVertex(val);
	if(ver!=NULL)return ver;
	ver=new Vertex(this->num,val);
	this->allVertexs[this->num]=ver;
	this->num++;
	return ver;
}
  • addEdge函数:添加顶点之间的关系。
/*
*添加顶点之间的关系
*/
void AOVGraph::addEdge(Vertex* from,Vertex* to,int weight) {
	this->edges[from->vid][to->vid]=weight;
}
  • topSort拓扑排序:核心代码。
/*
*拓扑排序算法
*/
void AOVGraph::topSort() {
	//队列
	queue<Vertex*> myQueue;
	int vid=0;
	Vertex* ver=NULL;
	int i=0;
	while( i<this->num ) {
		//以列优先扫描,在邻接矩阵中查找入度为 0 的第一个顶点
		for(int col=0; col<this->num; col++) {
			int find=1;
			ver=this->findVertex(col);
			//已经访问过,则查找下一个顶点
			if(ver->isVisited==true)continue;
			for(int row=0; row<this->num; row++) {
				if(this->edges[row][col]==1) {
					find=0;
					break;
				}
			}
			if(find==1) {
				vid=col;
				//找到后入队列
				myQueue.push( ver );
				ver->isVisited=true;
				//删除以此顶点为入度的边(关系)
				for(int col=0; col<this->num; col++) {
					if( this->edges[vid][col]==1 ) {
						this->edges[vid][col]=0;
					}
				}
				break;
			}
		}
		i++;
	}
	//检查图中是否有回路,如果图中所有顶点全部进入了队列则认为无环
	if(myQueue.size()==this->num)cout<<"图中无环"<<endl;
	else cout<<"图中有环"<<endl;
	cout<<"拓扑排序结果"<<endl;
	//输出队列中信息
	while(!myQueue.empty()) {
		Vertex* ver=myQueue.front();
		cout<<ver->val <<"\t";
		myQueue.pop();
	}
	cout<<endl;
}
  • 测试:
int main(int argc, char** argv) {
	AOVGraph* aovGraph=new AOVGraph();
	Vertex* from=aovGraph->addVertex("面向对象"); //0
	Vertex* to=	aovGraph->addVertex("多线程"); //1
	aovGraph->addEdge(from,to,1);

	to=	aovGraph->addVertex("IO流"); //2
	aovGraph->addEdge(from,to,1);

	to=	aovGraph->addVertex("网络编程"); //3
	aovGraph->addEdge(from,to,1);

	Vertex* temp=to;
	to=	aovGraph->addVertex("JDBC"); //4
	aovGraph->addEdge(temp,to,1);

	from=aovGraph->addVertex("数据库"); //5
	aovGraph->addEdge(from,to,1);
	aovGraph->show();

	aovGraph->topSort();
	aovGraph->show();
	return 0;
}

输出结果: 拓扑排序后,显示顶点关系时,可看到关系信息已经全部被抹去。

13.png

3.总结

邻接表的底层逻辑和邻接矩阵是没有差异性的。区别在于,存储方式的不同,对查找入度为 0的顶点的逻辑不同。

在邻接表中查找顶点的出度数量较方便,为了提高查找入度量的性能,在设计顶点类时,可以添加一个入度域,用来记录入度的数量。

受限篇幅,基于邻接表的拓扑排序以及AOE的相关知识在下一章节中讲解。

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

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

相关文章

MLX90640 热成像 STM32

点击此处了解详情点击此处了解详情点击此处了解详情点击此处了解详情点击此处了解详情 1、描述 这是一款手持式多功能热像仪&#xff0c;小巧轻便&#xff0c;搭载3.2英寸TFT显示屏、MLX90640热红外探头&#xff0c;锂电池供电&#xff0c;可以在各种场合使用&#xff0c;温度…

( “树” 之 DFS) 572. 另一棵树的子树 ——【Leetcode每日一题】

572. 另一棵树的子树 给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tr…

Densely Connected Convolutional Networks(引言翻译(有选择性))

翻译得有可能会不太专业&#xff0c;望见谅的同时&#xff0c;如果有些地方翻译错了&#xff0c;欢迎批评指正&#xff01; as information about the input or gradient passes through many layers, it can vanish and "wash out" by the time it reaches the end …

淄博烧烤、洛阳汉服......爆火,揭秘实体店客流爆满的秘诀!

淄博烧烤、云南泼水、洛阳穿越...... 沉寂了3年后&#xff0c;线下实体消费终于又开始火热起来&#xff0c;临近五一小长假&#xff0c;国内外旅游订单出现井喷式增长&#xff0c;线下消费持续迎来新一轮的高峰。 而这些热点&#xff0c;也带动了周边很多相关的实体店&#xff…

美团外卖平台的部分外卖 SPU数据实操练习

一、环境要求 Hadoop hive spark hbase开发环境 开启hadoop&#xff1a;start-all.sh开启zookeeper&#xff1a;zkServer.sh start开启hive&#xff1a;nohup hive --service metastore &nohup hive --service hiveserver2 & 打开hive界面&#xff1a;beeline -u j…

Ubuntu16.04安装NCNN和Opencv

一、安装NCNN 官网&#xff1a;NCNN官方文档 On Debian, Ubuntu or Raspberry Pi OS, you can install all required dependencies using: 首先安装NCNN的依赖项&#xff0c;根据官网安装以下依赖项。 sudo apt install build-essential git cmake libprotobuf-dev protobuf-…

dc-4靶机渗透

1.信息搜集&#xff0c;扫描存活主机&#xff0c;扫描端口&#xff0c;服务,发现开放80&#xff0c;22端口&#xff0c;cms没有看到 nmap 192.168.85.0/24 nmap -p1-66535 192.168.85.175 nmap -sv 192.168.85.1752.访问网站&#xff0c;发现登录框&#xff0c;根据提示&#…

360安全卫士 - 设置技巧 / 关闭广告

360安全卫士 - 设置技巧 / 关闭广告前言同步设置基本设置弹窗设置开机小助手安全防护中心漏洞修复木马查杀功能大全管理360文件夹游戏管家健康助手前言 360安全卫士是一款免费的PC安全软件&#xff0c;拥有垃圾清理、病毒查杀、启动项管理等功能。虽然有一些广告&#xff0c;但…

【Python】python技能树之包含元祖的列表升降序

文章目录前言一、实际操作二、使用步骤总结前言 大家都知道&#xff0c;在Python里面可以使用.sort方法或者sorted函数对各种数据进行排序&#xff0c;例如&#xff1a; 一、实际操作 a [2, 3, 1, 9, 3, 7, 4] a.sort() b [2, 3, 1, 9, 3, 7, 4] c sorted(b, reverseTrue…

Linux中的网络

文章目录一 、查看网络配置1.2 route命令—查看路由条目1.3 1.3hostname命令—查看主机名称1.4netstat命令—查看网络连接情况二 、测试网络连接2.1 ping 命令2.2 traceroute命令—跟踪数据包的路由途径2.3 mtr—动态跟踪网络2.4 nslookup命令—测试域名解析三 、使用网络配置命…

迎难而上,阿里高频考点2023Java岗面试突击手册

上周我接到一位粉丝的私信说目前互联网形势实在对他太不友好&#xff0c;感觉自己每个技术栈都会一点&#xff0c;但不是完全精通。基本二面三面的时候就挂了&#xff0c;已经完全不知道该朝哪个方向努力了&#xff0c;希望可以给他一些建议和方法指导。那么&#xff0c;本次就…

[golang gin框架] 20.Gin 商城项目-商品模块功能

一.商品模块数据表ER图关系分析商品模块数据表相关功能关系见:[golang gin框架] 16.Gin 商城项目-商品模块数据表ER图关系分析二.商品相关界面展示商品列表该商品列表有如下功能1.增加商品按钮:跳转到增加商品页面2.搜索功能:输入商品名称,点击搜索3.修改商品字段(上架,精品,新…

软件测试入门第一步:编写测试报告

什么是测试报告&#xff1f; 1、说明&#xff1a;是指把测试的过程和结果写成文档&#xff0c;对发现的问题和缺陷进行分析&#xff0c;为纠正软件的存在的质量问题提供依据&#xff0c;同时为软件验收和交付打下基础。 ps. 【测试过程和测试结果的分析报告&#xff0c;以及上线…

大爽pygame入门教程 第一节 基础知识 练习提示与答案

作者自我介绍&#xff1a;大爽歌, b站小UP主 &#xff0c;编程1对1辅导老师 1 逐行展示 思路提示 点击触发 之前的多行展示&#xff0c;是通过循环实现的。 这一回要点击触发一行的展示&#xff0c;不能直接使用循环了。 这里我们往更深层次去思考一下&#xff1a; 之前循环…

【CSS】margin 外边距负值使用案例 ( 正常外边距 | 使用外边距负值实现边框重叠 | 重叠边框突出显示 )

文章目录一、正常外边距案例二、使用外边距负值实现边框重叠三、重叠边框突出显示案例1、使用相对定位2、使用 z-index 设置定位盒子层级一、正常外边距案例 margine 正常情况下使用 , 设置 float 浮动 , 使得相邻的盒子模型紧贴在一起 ; 如果设置边框 , 则相邻的边框会重叠在…

A Comprehensive Capability Analysis of GPT-3 and GPT-3.5 Series Models论文学习

一、概述 Motivation&#xff1a;GPT系列的模型&#xff0c;像GPT-3&#xff0c;CodeX&#xff0c;InstructGPT&#xff0c;ChatGPT&#xff0c;尽管很多人关注他们能力的不同&#xff0c;但是很少关注GPT系列模型随着时间变化其能力的变化情况。 Methods&#xff1a; 在9个NL…

【LeetCode】轮转数组

&#x1f47b;内容专栏&#xff1a;《LeetCode刷题专栏》 &#x1f428;本文概括&#xff1a;189.轮转数组 &#x1f43c;本文作者&#xff1a;花 碟 &#x1f438;发布时间&#xff1a;2023.4.12 目录 思想1 暴力求解 代码实现&#xff1a; 思想2 三次倒置 代码实现&#…

Linux使用宝塔面板搭建网站,并内网穿透实现公网访问

文章目录前言1. 环境安装2. 安装cpolar内网穿透3. 内网穿透4.固定http地址5. 配置二级子域名6.创建一个测试页面前言 宝塔面板作为简单好用的服务器运维管理面板&#xff0c;它支持Linux/Windows系统&#xff0c;我们可用它来一键配置LAMP/LNMP环境、网站、数据库、FTP等&…

Java阶段二Day02

Java阶段二Day02 文章目录Java阶段二Day02SpringMVC的部分主流程HTTP请求Request1&#xff1a;请求行2&#xff1a;消息头3&#xff1a;消息正文HTTP响应Response1&#xff1a;状态行2&#xff1a;响应头3&#xff1a;响应正文通过版本迭代仿写SpringBootV1BirdBootApplication…

云安全—etcd未授权漏洞cert证书遗失

0x00 前言 今儿来看看etcd未授权漏洞以及cert证书遗失问题 0x01 etcd简述 1.etcd是什么 etcd是采用go语言编写的一个分布式的key-value存储。 2.etcd作用 etcd主要用于解决集群管中的OS升级的分布式并发控制以及配置文件的存储与分发等问题。在kubernetes集群中&#xff…