拓扑排序详解及C++实现

这篇具有很好参考价值的文章主要介绍了拓扑排序详解及C++实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

拓扑排序详解及C++实现

定义

百度百科定义如下:

拓扑排序,是对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在v之前。

很显然这一段话不是人话十分晦涩难懂,令人深思。

有向无环图

图论基础知识可以参考图论(一)基本概念_图论是什么_翟羽嚄的博客-CSDN博客。

需要注意:有向无环图不一定是树,例如:

拓扑排序c++代码,OIer的学习笔记,算法,c++,算法,拓扑学,图论,蓝桥杯

拓扑序列

对于一个有向无环图将图中的顶点排成一个序列,其中每个边的起点在序列中一定在终点之前;

(↑↑ 不是人话

通俗一点解释为:将一张图“压扁”,使顶点从左到右排成序列,且压扁后每条边的方向只能朝右,那么这个序列是这张图的拓扑序列。

例如这张图:

拓扑排序c++代码,OIer的学习笔记,算法,c++,算法,拓扑学,图论,蓝桥杯

其拓扑序列为1-2-4-3

拓扑排序c++代码,OIer的学习笔记,算法,c++,算法,拓扑学,图论,蓝桥杯

对于同一张图,可能存在不止一个拓扑序列。例如上图的另一个拓扑序列为1-4-2-3

拓扑排序c++代码,OIer的学习笔记,算法,c++,算法,拓扑学,图论,蓝桥杯

拓扑排序

那么拓扑排序可表示为:计算一个有向无环图的拓扑序列。

逻辑

拓扑排序的逻辑如下:

  1. 集合 S S S表示所有入度为0的点;队列 L L L表示拓扑序列。初始时为空。
  2. 找到所有入度为0的点,放入 S S S中。
  3. S S S中取出一个点 u u u,放入 L L L中。
  4. 在图中删除 s s s,并删除所有以 s s s为起点的边。
  5. 重复2~4,直到 S S S为空。

手动模拟拓扑排序的步骤如下:

手动模拟内容较长,如已经理解可以跳过

对于这张图:

拓扑排序c++代码,OIer的学习笔记,算法,c++,算法,拓扑学,图论,蓝桥杯

其中入度为0的点为1,因此 S S S L L L初始化如下:

拓扑排序c++代码,OIer的学习笔记,算法,c++,算法,拓扑学,图论,蓝桥杯

1 S S S中取出并放入 L L L

拓扑排序c++代码,OIer的学习笔记,算法,c++,算法,拓扑学,图论,蓝桥杯

删除点1和所有以1为起点的边:

拓扑排序c++代码,OIer的学习笔记,算法,c++,算法,拓扑学,图论,蓝桥杯

此时23的入度更新为0,所以将23放入 S S S

拓扑排序c++代码,OIer的学习笔记,算法,c++,算法,拓扑学,图论,蓝桥杯

重复上述步骤。将2 S S S中取出、放入 L L L;删除2和以2为起点的边;将6推入队列:

拓扑排序c++代码,OIer的学习笔记,算法,c++,算法,拓扑学,图论,蓝桥杯

继续重复此步骤,分别从 S S S中取3654789

拓扑排序c++代码,OIer的学习笔记,算法,c++,算法,拓扑学,图论,蓝桥杯

拓扑排序c++代码,OIer的学习笔记,算法,c++,算法,拓扑学,图论,蓝桥杯

拓扑排序c++代码,OIer的学习笔记,算法,c++,算法,拓扑学,图论,蓝桥杯

拓扑排序c++代码,OIer的学习笔记,算法,c++,算法,拓扑学,图论,蓝桥杯

拓扑排序c++代码,OIer的学习笔记,算法,c++,算法,拓扑学,图论,蓝桥杯

拓扑排序c++代码,OIer的学习笔记,算法,c++,算法,拓扑学,图论,蓝桥杯

拓扑排序c++代码,OIer的学习笔记,算法,c++,算法,拓扑学,图论,蓝桥杯

此时 S S S为空,所以结束计算。

拓扑序列为1 2 3 6 5 4 7 8 9

拓扑排序c++代码,OIer的学习笔记,算法,c++,算法,拓扑学,图论,蓝桥杯

代码实现

读入部分

使用链式向前星储存图:

struct edge
{
	int to;
	int next;
};

int head[100005];
edge graph[100005];
int n, m;

读入图:

cin >> n >> m;
for (int i = 1; i <= m; i++)
{
	int s, e;
	cin >> s >> e;

	graph[i].next = head[s];
	graph[i].to = e;
	head[s] = i;
}

由于需要计算点的入度,因此使用d数组储存节点的入度:

int d[100005];

在读入图时计算:

d[e]++;

读图部分完整代码为

#include <iostream>
using namespace std;

struct edge
{
	int to;
	int next;
};

int head[100005];
edge graph[100005];
int d[100005];
int n, m;

int main()
{
	cin >> n >> m;
	for (int i = 1; i <= m; i++)
	{
		int s, e;
		cin >> s >> e;

		graph[i].next = head[s];
		graph[i].to = e;
		head[s] = i;
		d[e]++;
	}
}

拓扑排序部分

使用队列que储存集合 S S S

queue<int> que;

遍历 1 ∼ N 1\sim N 1N,将入度为0(d[i] == 0)的点插入队列:

for (int i = 1; i <= n; i++)
{
	if (d[i] == 0)
	{
		que.push(i);
	}
}

que不为空时,从que中取出一个点 u u u,并输出(加入拓扑序列 L L L

while (!que.empty())
{
	int now = que.front();
	que.pop();
	cout << now << " ";
}

删除所有以 u u u为起点的边;实现时不需要真正从图中删除,直接将终点的入度减1即可:

for (int i = head[now]; i != 0; i = graph[i].next)
{
	d[graph[i].to]--;
}

如果某个点的入度更新后变为0,那么将这个点推入que

for (int i = head[now]; i != 0; i = graph[i].next)
{
	d[graph[i].to]--;
	if (d[graph[i].to] == 0)
	{
		que.push(graph[i].to);
	}
}

拓扑排序部分完整代码为:

...

queue<int> que;

...

int main()
{
	...

	while (!que.empty())
	{
		int now = que.front();
		que.pop();
		cnt--;
		cout << now << " ";
		for (int i = head[now]; i != 0; i = graph[i].next)
		{
			d[graph[i].to]--;
			if (d[graph[i].to] == 0)
			{
				que.push(graph[i].to);
			}
		}
	}
}

拓扑排序判环

上文中提到拓扑排序的只能对有向无环图进行排序。

如果图中出现环,则 S S S为空时图不为空。

利用这个特点,可以判断有向图中是否存在环。

使用cnt变量计算que中的元素数量(出队元素数量):

...
int main()
{
	...
	
	int cnt = n;

	while (!que.empty())
    ...
}

每出队一个元素,cnt--

while (!que.empty())
{
	int now = que.front();
	que.pop();
	
	cnt--;
	
	...
}

如果计算结束后图不为空,即cnt > 0,则图中有环:文章来源地址https://www.toymoban.com/news/detail-768143.html

if (cnt) // cnt > 0
{
	cout << "Circle!" << endl;
}

完整代码

#include <iostream>
#include <queue>
using namespace std;

struct edge
{
	int to;
	int next;
};

int head[100005];
edge graph[100005];

int d[100005];
int n, m;

queue<int> que;

int main()
{
	cin >> n >> m;
	for (int i = 1; i <= m; i++)
	{
		int s, e;
		cin >> s >> e;

		graph[i].next = head[s];
		graph[i].to = e;
		head[s] = i;
		d[e]++;
	}

	for (int i = 1; i <= n; i++)
	{
		if (d[i] == 0)
		{
			que.push(i);
		}
	}

	int cnt = n;

	while (!que.empty())
	{
		int now = que.front();
		que.pop();
		cnt--;
		cout << now << " ";
		for (int i = head[now]; i != 0; i = graph[i].next)
		{
			d[graph[i].to]--;
			if (d[graph[i].to] == 0)
			{
				que.push(graph[i].to);
			}
		}
	}

	cout << endl;
	if (cnt)
	{
		cout << "Circle!" << endl;
	}
}

到了这里,关于拓扑排序详解及C++实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++冒泡排序代码实现

    冒泡排序是一组数据元素中的相邻元素两两比较,如果满足前一个元素(i)比后一个元素(i+1)大则这两个元素交换,否则向后比较元素(i+1)和元素(i+2),这只是一轮排序。有n个元素需要排n-1轮,每一轮的排序都不用比较之前已经排好的数据,所以第i轮的比较次数为(n-i-1)次。(个人

    2024年02月16日
    浏览(43)
  • C++学习笔记-代码规范合集

    1. C++技巧 头文件扩展名包括“.h”、“.hpp”、“hxx”,源文件扩展名包括“.c”、“.cpp”、“cxx”。 关于改名。比如想将在Visual Studio中将某个函数/类的名字重新修改一下,一个一个改就很麻烦。若为函数,可以直接在函数名上右键“快速操作与重构”;若为类,可以直接在

    2024年02月13日
    浏览(39)
  • 拓扑排序详解(包含算法原理图解、算法实现过程详解、算法例题变式全面讲解等)

    在图论中,如果一个有向图无法从某个顶点出发经过若干条边回到该点,则这个图是一个有向无环图(DAG图)。 如图所示。 对于一个有向图,若x点指向y点,则称x点为y点的入度。 对于一个有向图,若x点指向y点,则称y点为x点的出度。 队列是一种特殊的线性表,特殊之处在

    2024年02月07日
    浏览(52)
  • C++学习笔记——用C++实现树(区别于C)

    树是一种非常重要的数据结构,它在计算机科学中的应用非常广泛。在本篇博客中,我们将介绍树的基本概念和C++中如何实现树。 目录 一、树的基本概念 2.C++中实现树 2.1创建一个树的实例,并向其添加节点 2.2三种遍历方式的实现代码 3.与C语言相比 3.1C++与C语言的一些不同之

    2024年01月17日
    浏览(45)
  • 数值分析:拉格朗日插值法笔记以及C++代码实现

    插值需求的诞生: 如何通过已知数据得到函数的近似解析表达式,从而获得更多的有用数据。 在实际应用中常常需要根据已知的函数点进行数据、模型的处理和分析,而有时候现有的数据是极少的,不足以支撑分析的进行,这时就需要使用一些数学的方法“模拟产生”一些

    2024年02月12日
    浏览(42)
  • 数字图像处理第三章 学习笔记附部分例子代码(C++ & opencv)

    本系列博客参考书为, 数字图像处理第三版-冈萨雷斯 第三版教材中图片下载地址: book images downloads vs2019配置opencv可以查看:VS2019 Opencv4.5.4配置教程 后续剧情: 数字图像处理 第四章 频率域滤波 学习笔记 数字图像处理 第六章 彩色图像处理 学习笔记 数字图像处理 第七章 小

    2024年02月03日
    浏览(83)
  • 学习笔记:C++环境下OpenCV的findContours函数的参数详解及优化

    这个是Visual Studio2019版本在OpenCV环境配置好后所显示的 6个参数 ,也即为全部参数 但是, 常用参数仅有四个 (参见程序里的第二行注释)  参数1    image  : 单通道图像矩阵。待提取轮廓的图像,可以是灰度图, 常用的是二值图 (C++中可选择使用Canny,拉普拉斯等边缘检测算法

    2024年02月07日
    浏览(41)
  • vector类模拟实现(c++)(学习笔记)

    基本框架: vector的大致形状如下:黄色代表每天满的地方。 使用初始化列表实现一个简单的无参构造函数。 记住要带[]即可。 因为push_back涉及到扩容函数,需要实现reserve()。 如下示例: 问题1:_finish赋值出错,出bug了,是因为size()函数,调用了空指针,导致报错。 改正:

    2024年02月05日
    浏览(41)
  • C++排序算法:归并排序详解

    一、归并排序 二、基本算法         1、分离         2、合并         3、图片讲解 三、代码实现         1、分离函数         2、合并函数          3、完整代码          4、样例 四、总结          归并排序 (Merge Sort)是建立在归并操作上的一种既有效又稳

    2024年02月12日
    浏览(40)
  • 机器学习笔记 - 基于C++的​​深度学习 三、实现成本函数

            作为人工智能工程师,我们通常将每个任务或问题定义为一个函数。         例如,如果我们正在开发面部识别系统,我们的第一步是将问题定义为将输入图像映射到标识符的函数F( X )。但是 问题是如何知道 F(X) 公式?         事实上,使用公式或一系列

    2024年02月13日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包