首页 > 其他分享 >拓扑排序 - TopoSort

拓扑排序 - TopoSort

时间:2023-05-10 19:55:21浏览次数:42  
标签:TopoSort int 拓扑 AOV 存图 排序 节点

拓扑排序 - TopoSort

前言

wcy 终于考上了心仪的大学,开启了精彩的大学生活!然而光是选课这一件事就把他难住了,因为一些课程包含先修课程:

课程编号 课程名称 先修课程
C1 高等数学
C2 程序设计基础
C3 离散数学 C1,C2
C4 数据结构 C2,C3
C5 算法语言 C2
C6 编译技术 C4,C5
C7 操作系统 C4,C9
C8 普通物理 C1
C9 计算机原理 C8

wcy 想排出一个顺序,让他可以丝滑地上完这九科课,那么这个顺序应该怎么排呢?


概念

AOV网

用节点表示活动,用弧表示活动之间的优先关系的有向图,叫做AOV网,即Activity On Vertex Network。

比如前言中给到的这个课程表,我们可以以图的形式把他画出来:先修课程图

这就是一个AOV网。

拓扑排序

拓扑排序指将AOV网中的节点排成一个线性序列,该序列必须满足:若从节点 \(i\) 到节点 \(j\) 有一条路径,则在该序列中节点 \(i\) 一定在节点 \(j\) 之前。

不难发现,如果一个AOV网有环,就必然无法得到这个AOV网的拓扑排序,因为他必然出现自己是自己前驱的情况。

所以,对有向图的节点进行拓扑排序后,如果所有节点都在拓扑序列中,那么就可以说明这个AOV网必然无环。

该AOV网是无环有向图 & 该AOV网可进行拓扑排序 互为充要条件。


实践

实现方法

大致分为两步走:

  1. 找入度为0的点
  2. 将该点纳入拓扑序列,在图中删除该点和该点的所有边

重复这个操作。

其间,我们可能需要一个中间容器储存所有入度为零且尚未纳入序列的点。这个容器依使用情况而定,栈、队列、优先队列均可。(甚至有时候可以不需要中间容器,每次处理时都搜一次点,甚至可以通过用DFS辅助等方法排序,用好有奇效哦)

代码

以栈为例的代码附上:

#include<bits/stdc++.h>
using namespace std;
int n,m,topo[MAXN],indegree[MAXN];
bool edge[MAXN][MAXN];
stack<int> s;
void TopoSort()
{
	int cnt=0;
	for(int i=1;i<=n;++i)
	{
		if(indegree[i]==0)
		s.push(i);
	}
	while(!s.empty())
	{
		int u=s.top();
		s.pop();
		topo[++cnt]=u;
		for(int j=1;j<=n;++j)
			if(edge[u][j])
				if(--indegree[j]==0)
					s.push(j);
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	memset(edge,0,sizeof(edge));
	memset(indegree,0,sizeof(indegree));
	for(int i=1;i<=m;++i)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		edge[u][v]=1;
		indegree[v]++;
	}
	TopoSort();
	for(int i=1;i<=n;++i)
		printf("%d ",topo[i]);
	return 0;
}

将前言中的例子送进来,就可以得到他的一个拓扑序列如图:

这样拓扑序列的特点就一目了然了。图中所有的箭头方向向后。

不唯一性

或者其实还有其他的拓扑序列:

所以可见,一个AOV网的拓扑序列是不唯一的。

关于写法

其实拓扑的写法还是很自由的,所以我们还可以通过其他方法来实现toposort.

比如使用搜索的写法——dfs:

#include<bits/stdc++.h>
using namespace std;
int n,m,indegree[MAXN],topo[MAXN];
stack<int>s;
vector<int>edge[MAXN];
bool dfs(int dep)
{
	if(dep>n)	return 1;
	if(dep==1)
		for(int i=1;i<=n;++i)
			if(!indegree[i])
				s.push(i);
	if(s.empty())	return 0;
	int u=s.top();
	s.pop();
	indegree[u]=-1;
	topo[dep]=u;
	for(int i=0;i<edge[u].size();++i)
		if(--indegree[edge[u][i]]==0)
			s.push(edge[u][i]);
	return dfs(dep+1);
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;++i)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		edge[u].push_back(v);
		indegree[v]++;
	}
	if(dfs(1))
		for(int i=1;i<=n;++i)
			printf("%d ",topo[i]);
	else
		printf("Impossible.");
	return 0;
}

当然,你甚至可以每次搜一遍入度,抛弃中间容器,这里就展示一种:

bool dfs(int dep)
{
	if(dep>n)	return 1;
	int temp=-1;
	for(int i=1;i<=n;++i)
		if(!indegree[i])	{temp=i;	break;}
	if(temp==-1)	return 0;
	indegree[temp]=-1;
	topo[dep]=temp;
	for(int i=0;i<edge[temp].size();++i)
		indegree[edge[temp][i]]--;
	return dfs(dep+1);
}

判断重边

对于邻接矩阵的存图方法,由于采用布尔值存图,重边会导致入度增加而邻接矩阵不变,必然出事。所以邻接矩阵请务必判重边。

if(!edge[u][v])
{
	edge[u][v]=1;
	indegree[v]++;
}

而邻接表,如 vector 存图则不需要判重,这是因为我们使用 vector 存图通常是存该点的子节点,所以在出现重边时该子节点在入度数组和 vector 中都会被记录两次,也就不会出现这个问题了。 (vector存图见上面dfs写法)


小题练手

NO.1 模板题 - 给火星人明明辈分

Genealogical tree

一道非常简单的板子题


NO.2 换个方式存图

[HNOI2015]菜肴制作

反向建边,vector 存图,优先队列作容器,细节拉满。


NO.3 抓住 Toposort 的特点

排序

这道题把握拓扑排序的核心步骤:检查入度为0的点。另外需要多测进行topo。


NO.4 Topo 全排列

Following Orders

Topo + DFS 可以想一下当初写全排列怎么写的


NO.5 拓上 DP(doge)

旅行计划

又是你们喜欢的DP哈哈哈哈哈


NO.6 绕个小弯

Labeling Balls

需要变通一下,非常有意思的一道题


NO.7 CCF 荣誉出品「君のNOIP」

[NOIP2020] 排水系统

果然是 NOIP 的题,,,就非常的离谱啊,他甚至卡 ull。所以现在面前的就只有四个选择了:

  1. 高精度
  2. 使用 64 位 GCCG++ 的 _int128
  3. 其实有一种方法,由于题中提到 \(d[i]≤5\) ,所以 其实所有出现的分数的分母都有且只有 2,3,5 这几个质因数,所以自然可以拆成这三个数的幂相乘的形式,也就解决了分母的高精危机。详见大佬123456zmy题解 P7113 【排水系统】
  4. 聪明的放弃最后一个点,在写完 gcd 等一系列分数运算后用 long long 拿到 90pts 卷铺盖走人。

暂时就告一段落了

标签:TopoSort,int,拓扑,AOV,存图,排序,节点
From: https://www.cnblogs.com/michaelwong007/p/toposort.html

相关文章

  • MySQL的随机排序(random orderby)
    MySQL的随机排序(randomorderby)是指在查询数据库时,将结果集以随机的方式排列。这种排序方式可以用于有趣的应用场景,例如实现随机音乐播放、广告推荐等。要实现MySQL的随机排序,可以使用RAND()函数。RAND()函数可以生成0-1之间的随机数,将它作为排序的依据即可。SELECT*FROM`my......
  • 冒泡排序
    importjava.util.Arrays;/***@Auther:么么*@Date:2023/5/8-05-08-22:16*@Description:PACKAGE_NAME*@version:1.0*///冒泡排序publicclasstest02{//这是一个main方法,是程序的入口:publicstaticvoidmain(String[]args){......
  • KingbaseES V8R6运维案例之---MySQL和KingbaseES字符串排序规则对比
    案例说明:相同数据排序后查询,在MySQL和KingbaseES下得到的排序顺序不一致,本案例从MySQL和KingbaseES的排序规则分析,两种数据库排序的异同点。适用版本:KingbaseESV8R6、MySQL8.0一、MySQL的排序规则1、排序规则(collation)排序规则是依赖于字符集,字符集是用来定义MySQL存储不......
  • 关于白话排序之快速排序以及维基百科的希尔排序
    1、白话排序的快速排序中,“坑”的形象概念,很好好好2、维基百科中,将每次的间隔的元素,不是采用白话中所用的A1、A2等的形式,而是采用直接放在一列,进行列排序的形象概念,很好好好3、希尔排序,最外边一层是gap的减少,里面两层---》插入排序。。。。。。......
  • vue sort 排序方法
    1、数据排序vararry=[9,5,6,7,5,6,3,1,0]arry.sort()//[0,1,3,5,5,6,6,7,9] 2、对象排序varlist=[{name:'张三',age:12},{name:'李四','age:23}];list.sort((a,b)=>{returna.age-b.age});//[{name:'李四','ag......
  • 背靠背两电平电路拓扑仿真。 前级为两电平整流器,网侧相电压有效值
    背靠背两电平电路拓扑仿真。前级为两电平整流器,网侧相电压有效值为220V。采用双闭环前馈解耦控制,实现并网单位功率因数,稳定直流母线电压,直流母线电压稳定在650V,网侧电流THD只有1.05%。后级为两电平逆变器,实现输出电压稳定在给定值,输出相电压为220V,输出电压THD只有0.51%。整个系统......
  • 简单选择排序
    简单选择排序算法思想:遍历整个数组,每一趟找出最小的那个数,放在数组前面importjava.util.Arrays;/***@Auther:么么*@Date:2023/5/8-05-08-22:05*@Description:PACKAGE_NAME*@version:1.0*///简单选择排序publicclasstest01{//这是一个m......
  • 排序算法
    1.插入排序voidinsert_sort(){for(inti=1;i<n;i++){intx=a[i];intj=i-1;while(j>=0&&x<a[j]){a[j+1]=a[j];j--;}a[j+1]=x;}......
  • JQ拖拽排序
    /***TableDnDplug-inforJQuery,allowsyoutodraganddroptablerows*Youcansetupvariousoptionstocontrolhowthesystemwillwork*Copyright(c)DenisHowlett<[email protected]>*LicensedlikejQuery,seehttp://docs.jquery.com/L......
  • 冒泡排序
    目录汇编实现及推导过程高级语言实现纸上得来终觉浅,绝知此事要躬行汇编实现及推导过程;程序名称:;功能:冒泡排序,方法5:不用两两比较,第一位数和其余数比较,小就交换;=======================================assumecs:code,ds:data;排序;手推算法;;外层循环条件si=0;......