首页 > 其他分享 >拓扑排序学习笔记

拓扑排序学习笔记

时间:2022-08-26 01:55:08浏览次数:90  
标签:队列 cur int 拓扑 笔记 AOV 序列 排序

前言

在学习这篇文章之前,你需要了解的算法有:

  1. 基本图论知识
  2. 链式前向星(图的一种存储方式)
  3. 了解 队列、栈 等简单数据结构的实现,用 STL 也行。

什么是拓扑排序

AOV网的定义

在了解拓扑排序(topo sort)之前,我们还得了解一个东西:AOV 网

AOV 网(Activity On Vertex) 主要用于表示活动间的优先关系。

看下面一个 非常不 生动的例子。

假设你在玩游戏,你想要解锁一个宝箱。

想要解锁这个宝箱,你需要先完成一堆任务,而每个任务又需要先完成子任务后才能解锁。

具体如下:

那么 AOV 网长啥样呢?

先观察这个 AOV 网,自己寻找规律。

现在,请思考一下 AOV 网是如何创建的。

(思考ing)

思考完了吧,其实规律是很好找的,就一句。

  • 如果 \(x \to y\),说明完成任务 \(y\) 之前要先完成任务 \(x\)。

比如,想完成 \(4\),必须先完成 \(1\) 与 \(6\)。

看一下图,\(6\) 与 \(1\) 是不是的确有一条边连向 \(4\) 呢?

进一步讲:

  • 如果存在一条路径使得 \(x\) 通向 \(y\),则完成任务 \(y\) 之前要先完成任务 \(x\)。

不过这一条件并没有太多作用。

以上就是 AOV 网大致的规律了,需要注意的是:

  • 如果图上存在环,必定没有办法完成全部任务。
  • 即,AOV 网必为一个 DAG(Directed Acycline Graph,有向无环图)。

拓扑排序的定义

说了这么一大坨,终于可以开始讲拓扑排序是神马东西了。

其实这东西特别简单,就是求一个序列,满足:

  • 按照序列的顺序完成任务,可以将全部任务完成

需要注意的是:

  • 拓扑序列应该为 \(1\) 到 \(n\) 的其中一个全排列。
  • 如果图上存在环,将没有拓扑序列(这一点前文提到过)。
  • 拓扑序列一般有多个,但是程序通常只能求其中一个

而拓扑排序,就是求出这个拓扑序列的。

下面,我们正式讲解拓扑排序的实现。

思路实现

1

用链式前向星存储这个图,并顺手计算每个点的入度。

不作解释,很简单。

int head[N], cur;
struct Node
{
	int now, nxt;
}e[M];
void add(int x, int y)
{
	e[++cur].now = y;
	e[cur].nxt = head[x];
	head[x] = cur;
}
int in[N]; //入度
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++)
{
    int x, y;
    scanf("%d%d", &x, &y);
    add(x, y); //x->y
    in[y]++;
}

2

准备一个队列,并将所有入度为 \(0\) 的点压入队列。

代码:

int cur = 0;
queue <int> Q;
for (int i = 1; i <= n; i++)
    if (in[i] == 0)
        Q.push(i);

3

每次都将队首计入 topo[] 数组中。

然后遍历所有队首点可以一次到达的点,如 \(1\) 要遍历到 \(2\)、\(3\)、\(4\)。

(正是因为这一点,我们采用链式前向星)

然后将遍历到的点 in[x]--

若当前点入度被减成 \(0\) 了,说明这个任务可以执行了,即:将当前点压入队列。

一直执行,直到队列为空。

代码:

int topo[N];
while (!Q.empty())
{
    int x = Q.front(); //截取队首
    Q.pop(); //弹出
    topo[++cur] = x;  //扔进拓扑序列中
    for (int i = head[x]; i; i = e[i].nxt)
    {
        int t = e[i].now; //易错,是 e[i].now 而非 i
        in[t]--;
        if (in[t] == 0) Q.push(t);
    }
}

4

最后,我们要判断是否存在拓扑序列。

很简单,不存在拓扑序列,当且仅当 \(\text{拓扑序列元素个数} < n\)。

为什么呢?

看这张图,显然是一个普通的环。

所有点入度都不为 \(0\),故不存在拓扑序列。

那如果外面还有一些点呢?

此处的点 \(a\)、\(b\)、\(c\)、\(d\) 之外可能还有其他点。

假设 点 \(a\)、\(b\)、\(c\)、\(d\) 已经入队了。

你会发现,点 \(a\)、\(b\)、\(c\)、\(d\) 仅仅能处理当前的边,所以最后仍然会留下这个环。

综上所述,可以轻松判断拓扑序列是否符合要求。

代码:

if (cur < n) printf("This graph isn't a DAG."); //图存在环
else
{
    for (int i = 1; i <= n; i++) printf("%d ", topo[i]);
}

总代码

完整代码

#include <iostream>
#include <cstdio>
#include <queue>
#define N 233
#define M 2005
using namespace std;
int head[N], cur;
struct Node
{
	int now, nxt;
}e[M];
void add(int x, int y)
{
	e[++cur].now = y;
	e[cur].nxt = head[x];
	head[x] = cur;
}
int n, m;
int in[N]; //入度
int topo[N];
bool topo_sort()
{
	cur = 0; //再次利用 
	queue <int> Q;
	for (int i = 1; i <= n; i++)
		if (in[i] == 0)
			Q.push(i);
	while (!Q.empty())
	{
		int x = Q.front();
		Q.pop();
		topo[++cur] = x;
		for (int i = head[x]; i; i = e[i].nxt)
		{
			int t = e[i].now;
			in[t]--;
			if (in[t] == 0) Q.push(t);
		}
	}
	return (cur == n);
}
int main()
{
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++)
	{
		int x, y;
		scanf("%d%d", &x, &y);
		add(x, y); //x->y
		in[y]++;
	}
	bool chk = topo_sort();
	if (chk == false) printf("This graph isn't a DAG."); //图存在环
	else
	{
		for (int i = 1; i <= n; i++) printf("%d ", topo[i]);
	}
    return 0;
}

测试样例:

6 8
1 2
1 3
1 4
3 2
3 5
4 5
6 4
6 5

有关『队列』的思考

其实,此处必须用队列吗?

了解了拓扑排序的原理后,很容易发现,不一定用队列。

比如,可以使用

观察一下两种数据结构的不同答案。

很明显,两种序列都是对的,因为前文提到过,拓扑序列并非唯一。

不过,两个序列并没有什么明显的规律呀!

有些题目会让你求:

一行,输出拓扑序列。

如果不存在拓扑序列,输出 \(-1\)。

如果有多个拓扑序列,输出字典序最小的那一个。

此时,你想到了什么?

没错!优先队列!

如果我们使用优先队列,就可以让拓扑序列相对有序了。

我们再列一个表格。

是不是稍微有一点规律了?!

此外,如果你给优先队列重载,就可以达到不同的需求了。

如果你重载成随机 true false,你就可以得到多个不同的拓扑序列了!

结语 & 参考文献

拓扑排序其实挺简单的,对吧!

参考了《算法训练营 海量题解+竞赛刷题:入门篇》一书,样例参照了此书。

本文的图均为作者所制,未经允许不可转载。图使用 https://csacademy.com/app/graph_editor/ 绘画。

前台所有表格都炸了,只好在后台截图。

今后会在本文加入例题的!

首发:2022-05-15 22:06:31

标签:队列,cur,int,拓扑,笔记,AOV,序列,排序
From: https://www.cnblogs.com/liangbowen/p/16622862.html

相关文章

  • 05.爬虫入门笔记1
    入门爬虫笔记011.request库的使用使用request库的get方法importrequestr=request.get('www.baidu.com')这会得到一个Response对象,将其存入变量r。显示得到的......
  • SSCMS文件解析-学习笔记
    //声明常量,不可变constfs=require('fs-extra');//初始化目录插件constdel=require('del');//删除文件的工具constgulp=require('gulp');//基于流的代码自动化......
  • 电子笔记本的思考(1)(ver0.4)
    章节: 电子笔记本的思考(1)(ver0.4)上面的是截屏的完整版,分割线下面的是纯文字版本: 作者姓名(本人的真实姓名):胡佳吉居住地:上海作者网名:EverSteins版权声明:电子笔记本的......
  • 探索JS中Object对象的key及key的排序
    首先,JavaScript中Object对象的key均为string类型的值。不过Object对象可以接受任意类型的值作为它的key,原因在于,我们为某个Object对象设定key的过程中会触发JavaScript的......
  • 学习笔记270—Excel如何快速批量将中文名字转换为拼音?
    Excel如何快速批量将中文名字转换为拼音?在excel表格中,我们可以通过内置的功能来进行拼音的编辑,但无法直接批量地转换中文为拼音。当然,这里是跳过了vba的用法,因为vba要求......
  • 性能测试学习笔记——工具的使用,性能测试流程
    性能测试学习笔记一、为什么要做性能测试:因为功能和接口测试只能验证软件的功能是否正常运行,功能和接口测试不能验证软件的性能在多用户,多并发,长时间的操作下,能否正常运......
  • Pendo for Mac v6.1.5中文版 云笔记软件
    前言哪里有轻量小巧的mac云笔记软件?PendoforMac是马克喵搜集到的一款运行在Mac平台上的一款新颖精美的mac云笔记软件。PendoMac版是Mac平台上的一款效率办公软件,Pend......
  • 2022-08-25 第五组 赖哲栋 学习笔记
    元素的设置<!--所有的HTMl元素,我们可以根据具体需求,自定义添加属性--><divhaha="abc"id="xyz"></div>获取属性的值元素.属性名的方式只适用于元素原生的属性......
  • 【Java高级编程】IO流学习笔记
    目录IO流File类文件/文件夹基础操作创建文件的完整步骤IO流-节点流读入文件一个字节(一个字节)[FileInputStream]字节数组的方式读取(读取全部内容)[FileInputStream]读取......
  • Android学习笔记五(JAVA):创建新的Activity,启动新的Activity,管理任务之定义启动模式,从
    本篇笔记给QuizDemo新增一个HelpActivity,用户点击Help按钮,会跳转到HelpActivity屏幕,并选择是否查看答案。查看答案之后,返回到答题屏幕,但是如果已经看了答案,这一题的作答就......