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

news2025/1/15 10:50:19

文章目录

    • 0)概述
    • 1)Kahn算法
      • 1:数据结构
      • 2:建图
      • 3:Kanh算法
    • 2)DFS染色
      • 1:数据结构
      • 2:建图
      • 3:DFS
    • 3)算法对比
    • 【例题】洛谷 B3644

推荐视频链接: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++算法模板】图的存储-邻接表,手撕链式前向星,超详细代码注释-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 【模板】拓扑排序 / 家谱树 - 洛谷

#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;
}

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

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

相关文章

力扣串题:反转字符串中的元音字母

​​​​​​​ 双指针&#xff0c;注意判断是否为元音的操作 bool IsVowel(char s){if(sa||se||si||so||su||sA||sE||sI||sO||sU) return true;return false; }char * reverseVowels(char * s){int len strlen(s),i0;while(i<len-1){if(IsVowel(s[i])&&IsVowel(s…

谷粒商城——分布式基础(全栈开发篇第一部分)

文章目录 一、服务治理网路数据支撑日志处理ELK应用监控集成工具开发工具 二、环境创建1、虚拟机创建2、虚拟机安装docker等1. 安装docker1. 配置阿里docker3.docker安装mysql错误 4、docker安装redis 3、软件1.Maven 阿里云镜像1.8jdk2、idea lombokmybatisX &#xff0c;3、 …

使用CIP采集欧姆龙EtherNet/IP从入门到精通

本文将会从以下几个方面介绍 1.CIP是什么 2.EtherNet/IP通信是什么 3.CIP通信报文解析 4.使用CIP常用的方法和功能介绍&#xff08;UCMM&#xff09; 5.自己封装了一个类&#xff0c;只要知道标签名称&#xff0c;和数据类型即可读写数据 6.demo展示 1.CIP是什么 CIP通信…

操作多级(一、二、三级)指针才是我们的该有的姿态~

Hello&#xff0c;很有缘在这篇文章上我们相遇了&#xff0c;那么我就用题目巩固我们多级指针的知识&#xff0c;当然这里的题目是比较有点难度的&#xff0c;我们需要有点基础呀&#xff0c;如果你能轻松理解题目那说明你对指针的了解已经很有基础了呢&#xff0c;那废话不多说…

Vue3自定义指令!!!

通过自定义指令实现菜单显示和权限控制问题。 一、新建一个在src目录下创建包directives&#xff0c;在包中创建一个ts文件。 import { useStore } from "/store/pinia";function hasRoles(role: any) {const pinaRoles useStore().roles;if (typeof role "s…

H5 流光分割个人主页源码

源码名称&#xff1a;流光分割个人主页源码 源码介绍&#xff1a;一款流光分割特效个人主页源码&#xff0c;源码带大量跳转个人联系方式按钮和朋友按钮。同时带有个人介绍。 需求环境&#xff1a;H5 下载地址&#xff1a; https://www.changyouzuhao.cn/10241.html

新品发布:广州大彩科技COF系列2.1寸480*480 IPS 串口屏发布!

一、产品介绍 该产品是一款2.1寸分辨率为 480480的医用级工业组态串口屏&#xff0c;拥有2.1寸IPS液晶屏&#xff0c;分辨率有480480&#xff08;实际显示为R240内切圆区域&#xff09;&#xff0c;支持电容触摸。采用COF超薄结构工艺设计&#xff0c;用户安装便捷灵活&#x…

REDHAWK——组件结构

文章目录 前言一、自动生成的组件文件1、为所有组件生成的文件2、为 C 组件生成的文件 二、自动生成的组件方法三、基础组件成员1、Ports2、Properties3、枚举①、C②、Java③、Python 4、Domain Awareness5、网络接口 四、组件实现五、管理和定义属性1、属性 ID2、属性名3、属…

C语言:字符串逆序输出, test ok

【问题描述】字符串逆序&#xff1a;设计函数功能是将一个字符串逆序&#xff0c;函数声明&#xff1a;void stringNx(char a[ ])&#xff0c;使用这个函数完成将输入的字符串逆序输出。 【输入形式】要求输入一个字符串 【输出形式】逆序后输出 【样例输入】abcd 【样例输…

Javaweb-MyBatis

一、概念 MyBatis是一款优秀的持久层框架&#xff0c;用于简化JDBC开发 MyBatis本是Apache的一个开源项目iBatis&#xff0c;2010年这个项目由apache software found迁移到了google code&#xff0c;并且改名为MyBatis。2013年11月迁移到Github 持久层 负责将数据到保存到数…

openGauss学习笔记-243 openGauss性能调优-SQL调优-典型SQL调优点-子查询调优

文章目录 openGauss学习笔记-243 openGauss性能调优-SQL调优-典型SQL调优点-子查询调优243.1 子查询调优243.1.1 子查询背景介绍243.1.2 openGauss对SubLink的优化243.1.3 更多优化示例 openGauss学习笔记-243 openGauss性能调优-SQL调优-典型SQL调优点-子查询调优 SQL调优是一…

【BOM笔记】基本概述、window对象常见事件、定时器、JS执行机制、location/navigator/history对象

文章目录 1 BOM概述1.1 什么是BOM1.2 BOM的构成 2 window 对象的常见事件2.1 窗口加载事件2.2 调整窗口大小事件 3 定时器3.1 setTimeout() 定时器3.2 setInterval() 定时器3.3 this 4 JS 执行机制4.1 JS 是单线程4.2 同步和异步4.3 JS 执行机制 5 location 对象5.1 属性5.2 方…

【深度学习笔记】9_9 语义分割和数据集

注&#xff1a;本文为《动手学深度学习》开源内容&#xff0c;部分标注了个人理解&#xff0c;仅为个人学习记录&#xff0c;无抄袭搬运意图 9.9 语义分割和数据集 在前几节讨论的目标检测问题中&#xff0c;我们一直使用方形边界框来标注和预测图像中的目标。本节将探讨语义分…

【欢迎投稿|稳定检索|高录用】2024年建筑土木与水利工程国际学术会议(ICBCHE 2024)

【欢迎投稿|稳定检索|高录用】2024年建筑土木与水利工程国际学术会议&#xff08;ICBCHE 2024) 苏老师 电话&#xff1a;19113133262&#xff08;微信同号&#xff09; QQ&#xff1a;2950880851 【投稿时请附言&#xff1a;icbche投稿苏老师推荐 将享有优先审稿及录用和学…

weblogic CVE-2023-21839详细复现

1、本次复现使用vulhub的靶场 切换到靶场的目录下&#xff0c;用docker -compose up -d启动靶场 使用docker-compose ps -a查看靶场的端口 2、访问开启的环境 3、准备工作都做好之后开始复现 &#xff08;1&#xff09;开启JNDIExploit 工具地址&#xff1a;GitHub - WhiteH…

HNU-计算机系统-实验1-原型机vspm1.0-(二周目玩家视角)

前言 二周目玩家&#xff0c;浅试一下这次的原型机实验。总体感觉跟上一年的很相似&#xff0c;但还是有所不同。 可以比较明显地感觉到&#xff0c;这个界面越来越好看了&#xff0c;可操作与可探索的功能也越来越多了。 我们HNU的SYSTEM真的越来越好了&#xff01;&#x…

使用C#创建服务端Web API

前言 C# Web API 是一种基于 .NET 平台&#xff08;包括但不限于.NET Framework 和 .NET Core&#xff09;构建 HTTP 服务的框架&#xff0c;用于创建 RESTful Web 服务。REST&#xff08;Representational State Transfer&#xff09;是一种软件架构风格&#xff0c;它利用HT…

Vue.js+SpringBoot开发APK检测管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 开放平台模块2.3 软件档案模块2.4 软件检测模块2.5 软件举报模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 开放平台表3.2.2 软件档案表3.2.3 软件检测表3.2.4 软件举报表 四、系统展示五、核心代…

鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:Flex)

以弹性方式布局子组件的容器组件。 说明&#xff1a; 该组件从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。Flex组件在渲染时存在二次布局过程&#xff0c;因此在对性能有严格要求的场景下建议使用Column、Row代替。Flex组…

【Spring】SpringMVC请求原理

什么是SpringMVC&#xff1a; SpringMVC 是Springframework中基于Java实现的Model-View-Controller框架&#xff0c;用于管理web应用程序的请求处理流程&#xff0c;并遵循MVC设计原则。 什么是MVC&#xff1a; MVC是Model-View-Controller的缩写&#xff0c;是一种设计原则&am…