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

文章目录

    • 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; // 题目中最大边数,拓扑排序是有向图建边,无需×2int 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; // 题目中最大边数,拓扑排序是有向图建边,无需×2int 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 0if(!dfs(j))return false;}c[x]=1; // 如果能够正常走掉dfs流程,则染色为1tp.push(x); // 进入拓扑序数组return true;
}bool toposort() {vector<int> tp; // 用vector存储便于反转memset(c,0,sizeof c); // 染色初始化为0for(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 secondusing namespace std;typedef long long ll;
typedef pair<int,int> PII;// 解题思路: const int N=1e5+5; // 最大顶点数
const int M=1e5+10; // 题目中最大边数,拓扑排序是有向图建边,无需×2int 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.mzph.cn/news/747141.shtml

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

相关文章

JS(一)变量与常量,作用域

JS&#xff08;一&#xff09;变量与常量,作用域 一变量与常量 在JavaScript中&#xff0c;关于变量声明有三种方式&#xff1a;var、let和const&#xff0c;它们之间有一些区别。 01 var 存在变量提升的问题&#xff0c;即变量可以在声明之前被访问。没有块级作用域&#…

4核8g服务器能支持多少人访问?价格感人,不知道性能如何

腾讯云轻量4核8G12M服务器配置446元一年&#xff0c;646元12个月&#xff0c;腾讯云轻量应用服务器具有100%CPU性能&#xff0c;系统盘为180GB SSD盘&#xff0c;12M带宽下载速度1536KB/秒&#xff0c;月流量2000GB&#xff0c;折合每天66.6GB流量&#xff0c;超出月流量包的流…

关于JVM虚拟机调优的20道高级面试题

1. 请解释什么是JVM内存模型&#xff1f; JVM内存模型是Java虚拟机在执行Java程序时&#xff0c;对内存进行逻辑划分的一种抽象模型。它定义了Java代码执行过程中的内存结构&#xff0c;包括以下几个主要区域&#xff1a; 程序计数器&#xff1a;每个线程都有一个独立的程序计…

Linux下Arthas(阿尔萨斯)的简单使用-接口调用慢排查

使用环境 k8s容器内运行了一个springboot服务&#xff0c;服务的启动方法是main()方法 下载并启动 arthas curl -O https://arthas.aliyun.com/arthas-boot.jar java -jar arthas-boot.jar选择应用 java 进程 就一个进程org.apache.catalina.startup.Bootstrap&#xff0c;输…

支小蜜AI校园防欺凌系统可以使用在宿舍吗?

随着人工智能技术的快速发展&#xff0c;AI校园防欺凌系统已成为维护校园安全的重要手段。然而&#xff0c;关于这一系统是否适用于宿舍环境&#xff0c;仍存在一些争议和讨论。本文将探讨AI校园防欺凌系统在宿舍中的适用性&#xff0c;分析其潜在的优势与挑战&#xff0c;并提…

Vue.js 应用实现监控可观测性最佳实践

前言 Vue 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建&#xff0c;并提供了一套声明式的、组件化的编程模型&#xff0c;帮助你高效地开发用户界面。无论是简单还是复杂的界面&#xff0c;Vue 都可以胜任。 TinyPro 是一套使用 Vue …

c/c++字符串处理标准库 string 介绍

c语言中string.h介绍 C语言的标准库中包含了一个头文件 <string.h>&#xff0c;该头文件提供了一系列字符串处理函数的声明和定义。以下是一些常用的函数&#xff1a; 字符串复制&#xff1a;strcpy(dest, src)。将源字符串 src 复制到目标字符串 dest&#xff0c;包括…

提升用户体验,Xinstall智能判定拉起技术来袭

在移动互联网时代&#xff0c;App推广已经成为各大企业的必争之地。然而&#xff0c;随着市场竞争的加剧&#xff0c;如何提升App的转化效率和用户体验成为了推广者们亟待解决的问题。这时&#xff0c;Xinstall的智能判定拉起技术应运而生&#xff0c;为推广者们带来了新的解决…

安卓百度地图API显示隐藏Marker

方法 BaiduMap.Marker.setVisible(boolean) 实现 List<Marker> list_marker new ArrayList<>(); boolean isShowMarker true;Override public boolean onCreateOptionsMenu(Menu menu) {String[] sm { "显隐信息", "显隐照片", "截…

一文彻底搞懂HTTP版本区别

文章目录 1. HTTP/1.0与HTTP/1.1区别1. 持久连接&#xff08;Persistent Connections&#xff09;2. 请求/响应的流水线化&#xff08;Pipeline&#xff09;3. 主机头字段&#xff08;Host Header Field&#xff09;4. 缓存控制5. 错误处理 2. HTTP/1.1与HTTP/2.0区别1. 多路复…

【leetcode】点名

最近考研正好复习到数据结构&#xff0c;趁着这个机会再刷点题&#xff0c;写了没时间更上来&#xff0c;看有特别思路的更一下 某班级 n 位同学的学号为 0 ~ n-1。点名结果记录于升序数组 records。假定仅有一位同学缺席&#xff0c;请返回他的学号。 示例 1: 输入: records…

开发小程序多少钱?大数据揭秘,小白必看的成本清单!

在数字化时代的浪潮中&#xff0c;小程序已经成为连接用户与服务的重要桥梁。它们无需下载、安装&#xff0c;即可实现快速访问和使用&#xff0c;为用户提供了便捷的体验。然而&#xff0c;不少企业和个体经营者在面对开发小程序时&#xff0c;往往会对成本问题抱有疑问&#…

SpringBoot内置tomcat支持JSP

SpringBoot默认是不支持JSP解析,需要使用tomcat内置的JSP解析功能,需要引入的相关jar包如下: <!---引入嵌入式tomcat用于支持网页解析---> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web<…

OJ_最大序列和

题干 C实现 #include <stdio.h> #include <algorithm> using namespace std;long long s[1000001]; long long dp[1000002];//dp[i]是前i个元素中必须包含右边缘的最大子序和int main() {int n;scanf("%d",&n);for(int i 0; i< n;i){scanf(&quo…

【Vue3】Vue3中路由规则的 props 配置

&#x1f497;&#x1f497;&#x1f497;欢迎来到我的博客&#xff0c;你将找到有关如何使用技术解决问题的文章&#xff0c;也会找到某个技术的学习路线。无论你是何种职业&#xff0c;我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章&#xff0c;也欢…

云计算 3月14号 (TCP三次握手和四次挥手)

1.TCP三次握手和四次挥手 1.TCP的传输过程&#xff1a; Seq 序列号 保障传输过程可靠。 ACK &#xff08;确认消息&#xff09; SYN &#xff08;在建立TCP连接的时候使用&#xff09; FIN &#xff08;在关闭TCP连接的时候使用&#xff09; 3.TCP建立连接的过程&…

让Python自动测试更得心应手——认识一下神奇的pytest测试框架

前言 Python在测试圈的应用非常广泛&#xff0c;特别是在自动化测试以及测试开发的领域&#xff0c;其中在自动化测试中我们常用的测试框架是uniitest和pytest&#xff0c;本文将带领大家搭建以及熟悉pytest的使用。 既然有unittest那么为什么还要用pytest呢&#xff1f; 这…

单目测距+姿态识别+yolov8界面+车辆行人跟踪计数

yolov5单目测距速度测量目标跟踪&#xff08;算法介绍和代码&#xff09; 1.单目测距实现方法 在目标检测的基础上&#xff0c;我们可以通过计算物体在图像中的像素大小来估计其距离。具体方法是&#xff0c;首先确定某个物体的实际尺寸&#xff0c;然后根据该物体在图像中的像…

Vue3--数据和方法

data 组件的 data 选项是一个函数。Vue 在创建新组件实例的过程中会自动调用此函数。   data选项通常返回一个对象&#xff0c;然后 Vue 会通过响应性系统将其包裹起来&#xff0c;并以 $data 的形式存储在组件实例中。 <!DOCTYPE html> <html lang"en"&g…