【C++算法模板】图论-拓扑排序,超详细注释带例题

这篇具有很好参考价值的文章主要介绍了【C++算法模板】图论-拓扑排序,超详细注释带例题。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

推荐视频链接:D01 拓扑排序

0)概述

  • 给定一张有向无环图,排出所有顶点的一个序列 A A A 满足:对于图中的每条有向边 ( x , y ) (x,y) (x,y) x x x A A A 中都出现在 y y y 之前,则称 A A A 是该图的顶点的一个拓扑序

  • 拓扑排序 可以判断有向图中是否有环,可以生成拓扑序列

  • 对于下图, { 2 , 3 , 5 , 1 , 7 , 4 , 6 } \{2,3,5,1,7,4,6\} {2,3,5,1,7,4,6} { 3 , 2 , 1 , 5 , 7 , 6 , 4 } \{3,2,1,5,7,6,4\} {3,2,1,5,7,6,4} 都是合法的拓扑序

拓扑序列c++模板,C++算法竞赛,算法,c++,图论,拓扑排序,算法竞赛,图搜索算法

复习一下链式前向星吧:【C++算法模板】图的存储-邻接表,手撕链式前向星,超详细代码注释-CSDN博客

1)Kahn算法

  • 算法核心:用队列维护一个入度为 0 0 0 的节点的集合
  1. 初始化(链式前向星建图建边),队列 q q q 压入所有入度为 0 0 0 的点
  2. 每次从 q q q 中取出队头 x x x 放入数组 t p tp tp t p tp tp 数组保存出队顺序,也就是拓扑序
  3. 然后将 x x x 的所有出边删除,如删除边 ( x , y ) (x,y) (x,y) y y y 的入度则 − 1 -1 1,如果 y y y 的入度变为 0 0 0,则将 y y y 压入 q q q 中,其中每个顶点的入度用数组 d d d 维护
  4. 不断重复 2 , 3 2,3 2,3 过程,直到队列 q q q 为空
  5. t p tp tp 中的元素个数等于 n n n,则有拓扑序;否则,有环

1:数据结构

const int N=1e5+5; // 最大顶点数
const int M=1e5+10; // 题目中最大边数,拓扑排序是有向图建边,无需×2

int d[N]; // 存储每个顶点的入度
queue<int> q; // 维护入度为0的顶点的队列
queue<int> tp; // 记录q中顶点的出队顺序(拓扑序)

int h[N]; // 存储每个顶点起始边的编号,默认-1表示无边相连
int e[M]; // e[i]:编号为i的边可达的顶点编号
int ne[M]; // ne[i]:编号为i的边的下一条边的编号是ne[i]
int idx; // 边的编号,建边因子

2:建图

// 链式前向星
void add(int a,int b) {
	e[idx]=b;
	ne[idx]=h[a]; // 头插法思想
	h[a]=idx++;
}

3:Kanh算法

// 拓扑序存储于tp队列中,如果能形成拓扑序返回true
bool tuopu() {
	for(int i=1;i<=n;i++) {
		// 如果入度为0则加入队列
		if(d[i]==0) q.push(i);
	}
	while(q.size()) {
		int x=q.front();
		q.pop();
		tp.push(x); // 出队顺序即拓扑序
		// 遍历x的所有出边
		for(int i=h[x];i=-1;i=ne[i]) {
			int j=e[i];
			// 如果去掉边(i,j)后j的入度变为0,则加入队列
			if(--d[j]==0) q.push(j);
		}
	}
	return tp.size()==n; // 如果能形成一个拓扑序,返回true,否则false
}

2)DFS染色

  • 算法核心:在于染色法,每次 d f s dfs dfs 搜索会给点变色,如果有拓扑序,每个点的颜色都会从 0 → − 1 → 1 0→-1→1 011 经历三次变色
  1. 初始化:将所有点染色为 0 0 0
  2. 枚举每个点,进入点 x x x,将 x x x 染色为 − 1 -1 1,随后枚举 x x x 的所有儿子结点 y y y,如果 y y y 的颜色仍为 0 0 0,说明该点未被遍历过,则递归到下一层;如果 y y y 的颜色为 − 1 -1 1,说明遍历到祖先节点了,即出现了环,则直接 r e t u r n return return
  3. 如果枚举完 x x x 的所有儿子节点都没有发现环,则把 x x x 染色为 1 1 1,并把 x x x 压入 t p tp tp 数组
  4. 注意,因为 D F S DFS DFS 是栈实现的,回溯的时候才把点加入 t p tp tp 数组,所以需要将 t p tp tp 数组逆序才能得到拓扑序

1:数据结构

const int N=1e5+5; // 最大顶点数
const int M=1e5+10; // 题目中最大边数,拓扑排序是有向图建边,无需×2

int c[N]; // 存储每个结点的颜色
vector<int> tp; // 存储拓扑序

int h[N]; // 存储每个顶点起始边的编号,默认-1表示无边相连
int e[M]; // e[i]:编号为i的边可达的顶点编号
int ne[M]; // ne[i]:编号为i的边的下一条边的编号是ne[i]
int idx; // 边的编号,建边因子

2:建图

// 链式前向星
void add(int a,int b) {
	e[idx]=b;
	ne[idx]=h[a]; // 头插法思想
	h[a]=idx++;
}

3:DFS

// dfs
bool dfs(int x) {
	c[x]=-1; // 先染色为-1
	// 遍历所有儿子节点
	for(int i=h[x];i=-1;i=ne[i]) {
		int j=e[i]; // 取出节点编号
		if(c[j]<0) return false; // 遍历到祖先节点,有环,直接return
		// 如果没有遍历过
		else if(!c[j])
			// 继续往下搜,自然结束return 0
			if(!dfs(j))
				return false;
	}
	c[x]=1; // 如果能够正常走掉dfs流程,则染色为1
	tp.push(x); // 进入拓扑序数组
	return true;
}

bool toposort() {
	vector<int> tp; // 用vector存储便于反转
	memset(c,0,sizeof c); // 染色初始化为0
	for(int i=1;i<=n;i++) {
		// 如果c没有被走过
		if(!c[i])
			// 如果遇到环则说明无法形成拓扑序
			if(!dfs(i))
				return 0;
	}
	reverse(tp.begin(),tp.end());
	return 1;
}

3)算法对比

  • 在实际使用拓扑排序时只需要掌握 K a h n Kahn Kahn 即可,因为更好理解, D F S DFS DFS 染色和二分图中的匈牙利算法的思想比较类似,这里只用了解即可
    • K a h n Kahn Kahn:队列维护,顺着拓扑序收集点
    • D F S DFS DFS:系统栈维护,逆着拓扑序收集点
  • 二者时间复杂度都为 O ( E + V ) O(E+V) O(E+V),其中 E E E 为边数, V V V 为点数

【例题】洛谷 B3644

题目链接:B3644 【模板】拓扑排序 / 家谱树 - 洛谷文章来源地址https://www.toymoban.com/news/detail-852291.html

#include<bits/stdc++.h>
#define x first
#define y second

using namespace std;

typedef long long ll;
typedef pair<int,int> PII;

// 解题思路: 

const int N=1e5+5; // 最大顶点数
const int M=1e5+10; // 题目中最大边数,拓扑排序是有向图建边,无需×2

int n; // 顶点数

int d[N]; // 存储每个顶点的入度
queue<int> q; // 维护入度为0的顶点的队列
queue<int> tp; // 记录q中顶点的出队顺序(拓扑序)

int h[N]; // 存储每个顶点起始边的编号,默认-1表示无边相连
int e[M]; // e[i]:编号为i的边可达的顶点编号
int ne[M]; // ne[i]:编号为i的边的下一条边的编号是ne[i]
int idx; // 边的编号,建边因子

// 链式前向星
void add(int a,int b) {
	e[idx]=b;
	ne[idx]=h[a]; // 头插法思想
	h[a]=idx++;
}

// 拓扑序存储于tp队列中,如果能形成拓扑序返回true
void tuopu() {
	queue<int> q;
	for(int i=1;i<=n;i++) {
		// 如果入度为0则加入队列
		if(d[i]==0) 
			q.push(i);
	}
	while(q.size()) {
		int x=q.front();
		q.pop();
		cout<<x<<' '; // 直接输出拓扑序
		tp.push(x); // 出队顺序即拓扑序
		// 遍历x的所有出边
		for(int i=h[x];i!=-1;i=ne[i]) {
			int j=e[i];
			// 如果去掉边(i,j)后j的入度变为0,则加入队列
			if(--d[j]==0) q.push(j);
		}
	}
}

int main() {
	cin>>n;
	memset(h,-1,sizeof h); // 链式前向星邻接表初始化
	for(int i=1;i<=n;i++) {
		int j;
		// 当j==0时退出循环
		while(cin>>j && j) {
			add(i,j);
			d[j]++; // 节点j的入度++
		}
	}
	tuopu();
	return 0;
}

到了这里,关于【C++算法模板】图论-拓扑排序,超详细注释带例题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • ⌈算法进阶⌋图论::拓扑排序(Topological Sorting)——快速理解到熟练运用

    目录  一、原理 1. 引例:207.课程表  2. 应用场景 3. 代码思路 二、代码模板 三、练习 1、210.课程表Ⅱ🟢 2、2392.给定条件下构造举证🟡 3、310.最小高度树🟡 4、 2603.收集树中金币 🔴 1. 引例:207.课程表 就如大学课程安排一样,如果要学习数据结构与算法、机器学习这类课程

    2024年02月11日
    浏览(43)
  • 拓扑排序 (算法思想+图解+模板+练习题)

    有向无环图一定是拓扑序列,有向有环图一定不是拓扑序列。 无向图没有拓扑序列。 首先我们先来解释一下什么是有向无环图: 有向就是我们两个结点之间的边是有方向的,无环的意思就是整个序列中没有几个结点通过边形成一个圆环。 下图就是一个有向无环图,它也一定

    2024年02月03日
    浏览(63)
  • 浅谈图论——迪杰斯特拉算法(leetcode例题,C++演示)

    如果你想问这个世界上什么算法是最牛逼的?博主是回答不上来的。但是,如果你问博主 什么数据结构是最牛逼 ?博主个人认为 图是最牛逼的数据结构 。因为很多的问题,都可以用图这种数据结构来表示。链表、树这种数据结构博主认为可以看成一种 特殊的图 。所以,博

    2024年02月20日
    浏览(80)
  • 拓扑排序例题 P4017 最大食物链计数

    题目链接:P4017 最大食物链计数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 你知道食物链吗?Delia 生物考试的时候,数食物链条数的题目全都错了,因为她总是重复数了几条或漏掉了几条。于是她来就来求助你,然而你也不会啊!写一个程序来帮帮她吧。 给你一个食物网,

    2023年04月25日
    浏览(47)
  • 【图论】拓扑排序

     拓扑排序是一种对有向无环图(DAG)进行排序的算法,使得图中的每个顶点在排序中都位于其依赖的顶点之后。它通常用于表示一些任务之间的依赖关系,例如在一个项目中,某些任务必须在其他任务之前完成。 拓扑排序的步骤如下: 找到入度为0的顶点:入度是指指向某

    2024年02月11日
    浏览(36)
  • 图论应用——拓扑排序

    拓扑排序的原理和宽度优先搜索差不多

    2024年04月26日
    浏览(46)
  • 第三章 图论 No.13拓扑排序

    拓扑序和DAG有向无环图联系在一起,通常用于最短/长路的线性求解 裸题:1191. 家谱树 1191. 家谱树 - AcWing题库 差分约束+拓扑排序:1192. 奖金 1192. 奖金 - AcWing题库 由于图中所有边权都是正数,可以直接使用topsort求解差分约束问题 根据题意,要求一个最小值,使用最长路求解

    2024年02月12日
    浏览(42)
  • 搜索与图论(一)(深搜,广搜,树与图的存储遍历,拓扑排序)

    往深里搜,搜到叶子结点那里,回溯,到可以继续到叶子结点深搜的位置。 1、回溯一定要恢复现场 2、定义一个与当前递归层数有关的终止条件(题目要求的东西) 3、每层都用循环判断是否存在可以dfs的路 输出数字组合 全排列的思想解决n皇后问题,用三个bool数组描述限制

    2024年02月19日
    浏览(45)
  • 【LeetCode 热题 100】图论 专题(bfs,拓扑排序,Trie树 字典树)

    from: https://leetcode.cn/studyplan/top-100-liked/ bfs 具有 边权为1 的最短路性质 拓扑排序,入度 Trie树, 高效存储 字符串【见鬼,不知道为什么写错,需要掌握熟练度】 dfs 写法,比较简洁 bfs 写法,有最短路性质 bfs 具有 边权为1 的最短路性质 拓扑排序 模板题 数组写法,简洁,需

    2024年02月13日
    浏览(49)
  • B3644 【模板】拓扑排序 / 家谱树

    有个人的家族很大,辈分关系很混乱,请你帮整理一下这种关系。给出每个人的后代的信息。输出一个序列,使得每个人的后辈都比那个人后列出。 第 1 1 1 行一个整数 N N N ( 1 ≤ N ≤ 100 1 le N le 100 1 ≤ N ≤ 100 ),表示家族的人数。接下来 N N N 行,第 i i i 行描述第 i i i

    2024年02月12日
    浏览(34)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包