拓扑排序与有向无环图 -- Kahn算法和深度优先搜索

拓扑排序与有向无环图

文章目录

  • 拓扑排序与有向无环图
    • 1. 什么是拓扑排序
      • 快速排序(Quick Sort)
      • 拓扑排序(Topological Sort)
      • 主要区别
    • 2. 拓扑排序与有向无环图之间的契合性
    • 3. Kahn算法实现拓扑排序
      • 算法思想
      • 算法步骤
      • 算法代码
    • 4. 深度优先搜索(DFS)实现拓扑排序
      • 算法思想
      • 算法步骤
      • 算法代码
    • 5. 总结

1. 什么是拓扑排序

核心思想:根据节点之间的依赖关系进行排序

拓扑排序并非等同于快排等常见算法的应用场景,下面对其进行对比以便明晰拓扑排序的使用场景和排序目的:

快速排序(Quick Sort)

  • 目的对一组数按大小顺序进行排序
  • 应用场景:用于对无序数组进行排序,使得数组中的元素按升序或降序排列。
  • 算法思想:通过选择一个基准元素,将数组分成两部分,一部分小于基准元素,另一部分大于基准元素,然后递归地对这两部分进行排序。

拓扑排序(Topological Sort)

  • 目的对有向无环图(DAG)中的节点进行排序,使得对于每一条有向边 ( u -> v ),节点 ( u ) 在排序结果中出现在节点 ( v ) 之前。
  • 应用场景:用于表示依赖关系的场景,例如任务调度、课程安排、编译顺序等。
  • 算法思想通过不断移除入度为0的节点,构建一个线性序列,使得每个节点都在其所有前驱节点之后。

主要区别

  • 数据结构:快排处理的是数组或列表,而拓扑排序处理的是有向无环图
  • 排序依据:快排根据元素的大小进行排序,而拓扑排序根据图中的依赖关系进行排序。
  • 结果:快排的结果是一个按大小顺序排列的数组,而拓扑排序的结果是一个满足依赖关系的节点序列

2. 拓扑排序与有向无环图之间的契合性

  1. 依赖关系:在有向无环图中,每条有向边 ( u -> v ) 表示节点 ( u ) 是节点 ( v ) 的前驱,或者说 ( v ) 依赖于 ( u )。拓扑排序的目标是找到一个线性序列,使得每个节点都在其所有前驱节点之后。
  2. 无环性:如果图中存在环,那么就无法找到一个满足所有依赖关系的线性序列。例如,如果存在一个环 ( u -> v -> w ->u ),那么 ( u ) 既需要在 ( v ) 之前,又需要在 ( v ) 之后,这是不可能的。

3. Kahn算法实现拓扑排序

算法思想

Kahn算法的核心思想是通过不断移除入度为0的节点来实现拓扑排序。核心步骤如下:

  1. 入度为0的节点:在有向无环图(DAG)中,入度为0的节点是没有任何前驱节点的节点。这些节点可以作为拓扑排序的起点。
  2. 移除节点:将入度为0的节点从图中移除,并将其加入拓扑排序结果中。然后,更新其所有邻接节点的入度。
  3. 重复过程:重复上述过程,直到所有节点都被移除。如果在某个时刻没有入度为0的节点,但仍有节点未被移除,则说明图中存在环,无法进行拓扑排序。

算法步骤

  1. 读取输入
    • 读取点的个数 ( n ) 和边的条数 ( m )。
    • 初始化一个邻接表 graph 和一个入度数组 in_degree,用于存储每个节点的入度。
  2. 构建图和入度数组
    • 读取每条边的信息,并更新邻接表和入度数组。
    • 邻接表 graph[u] 存储从节点 ( u ) 出发的所有边的终点。
    • 入度数组 in_degree[v] 记录节点 ( v ) 的入度,即有多少条边指向 ( v )。
  3. 初始化队列
    • 创建一个队列 q,用于存储所有入度为0的节点。
    • 遍历所有节点,将入度为0的节点加入队列。
  4. 拓扑排序
    • 创建一个向量 topo_order,用于存储拓扑排序结果。
    • 当队列不为空时,执行以下操作:
      • 从队列中取出一个节点 node,将其加入 topo_order
      • 遍历 node 的所有邻接节点 neighbor,将这些邻接节点的入度减1。
      • 如果某个邻接节点的入度变为0,则将其加入队列。
  5. 检查结果
    • 如果 topo_order 的大小等于节点数 ( n ),则输出拓扑排序结果。
    • 否则,输出 -1,表示图中存在环,无法进行拓扑排序。

算法代码

// 假定有如下题目:
/*
给定一个包含 n 个点 m 条边的有向无环图,求出该图的拓扑序。若图的拓扑序不唯一,输出任意合法的拓扑序即可。若该图不能拓扑排序,输出-1。
输入描述:
第一行输入两个整数 n,m(1≤n,m<2·10^5),表示点的个数和边的条数。
接下来的 m 行,每行输入两个整数 Ui,Vi(1≤u,v≤n),表示 Ui 到 Vi 之间有一条有向边。
输出描述:
若图存在拓扑序,输出一行 n 个整数,表示拓扑序。否则输出 -1。
*/#include <iostream>
#include <vector>
#include <queue>int main() {int n, m;std::cin >> n >> m;std::vector<std::vector<int>> graph(n + 1);std::vector<int> in_degree(n + 1, 0);// 读取边并构建图和入度数组for (int i = 0; i < m; ++i) {int u, v;std::cin >> u >> v;graph[u].push_back(v);in_degree[v]++;}std::queue<int> q;// 将所有入度为0的节点加入队列for (int i = 1; i <= n; ++i) {if (in_degree[i] == 0) {q.push(i);}}std::vector<int> topo_order;while (!q.empty()) {int node = q.front();q.pop();topo_order.push_back(node);// 遍历当前节点的所有邻接节点for (int neighbor : graph[node]) {in_degree[neighbor]--;if (in_degree[neighbor] == 0) {q.push(neighbor);}}}// 检查是否所有节点都被排序if (topo_order.size() == n) {for (int i = 0; i < n; ++i) {std::cout << topo_order[i] << " ";}std::cout << std::endl;} else {std::cout << -1 << std::endl;}return 0;
}

4. 深度优先搜索(DFS)实现拓扑排序

算法思想

DFS方法的核心思想是利用递归的回溯特性,在回溯时将节点加入拓扑排序结果中。这样可以确保每个节点都在其所有后继节点之前被处理。通过这种方式,DFS能够有效地找到一个合法的拓扑排序,或者检测到图中是否存在环。

算法步骤

  1. 读取输入
    • 读取点的个数 ( n ) 和边的条数 ( m )。
    • 初始化一个邻接表 graph 和一个访问标记数组 visited
  2. 构建图
    • 读取每条边的信息,并更新邻接表 graph
  3. 深度优先搜索
    • 对每个未访问的节点调用DFS函数。
    • 在DFS过程中,递归访问所有邻接节点。
    • 在回溯时,将当前节点压入栈 topo_stack
  4. 输出结果
    • 检查栈中的节点数是否等于 ( n )。如果不等于,说明图中存在环,无法进行拓扑排序,输出 -1。
    • 否则,依次弹出栈中的节点,输出拓扑排序结果。

算法代码

// 假定有如下题目:
/*
给定一个包含 n 个点 m 条边的有向无环图,求出该图的拓扑序。若图的拓扑序不唯一,输出任意合法的拓扑序即可。若该图不能拓扑排序,输出-1。
输入描述:
第一行输入两个整数 n,m(1≤n,m<2·10^5),表示点的个数和边的条数。
接下来的 m 行,每行输入两个整数 Ui,Vi(1≤u,v≤n),表示 Ui 到 Vi 之间有一条有向边。
输出描述:
若图存在拓扑序,输出一行 n 个整数,表示拓扑序。否则输出 -1。
*/#include <iostream>
#include <vector>
#include <stack>void dfs(int node, const std::vector<std::vector<int>>& graph, std::vector<bool>& visited, std::stack<int>& topo_stack) {visited[node] = true;for (int neighbor : graph[node]) {if (!visited[neighbor]) {dfs(neighbor, graph, visited, topo_stack);}}topo_stack.push(node);
}int main() {int n, m;std::cin >> n >> m;std::vector<std::vector<int>> graph(n + 1);std::vector<bool> visited(n + 1, false);std::stack<int> topo_stack;// 读取边并构建图for (int i = 0; i < m; ++i) {int u, v;std::cin >> u >> v;graph[u].push_back(v);}// 对每个节点进行DFSfor (int i = 1; i <= n; ++i) {if (!visited[i]) {dfs(i, graph, visited, topo_stack);}}// 检查是否所有节点都被访问if (topo_stack.size() != n) {std::cout << -1 << std::endl;} else {// 输出拓扑排序结果while (!topo_stack.empty()) {std::cout << topo_stack.top() << " ";topo_stack.pop();}std::cout << std::endl;}return 0;
}

5. 总结

拓扑排序是一种用于有向无环图(DAG)的排序算法,旨在根据节点之间的依赖关系生成一个线性序列。本文详细介绍了两种实现拓扑排序的方法:Kahn算法和深度优先搜索(DFS)Kahn算法通过不断移除入度为0的节点来实现排序,而DFS方法利用递归的回溯特性在回溯时将节点加入排序结果中。两种方法都能有效地检测图中是否存在环,并生成合法的拓扑排序。

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

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

相关文章

线性回归从零实现

《李沐动手学深度学习》P30 import numpy as np import matplotlib.pyplot as plt import random# y x * w b noise # x dimension: (num_samples, 2) # w dimension: (2, 1) # b dimension: (1, 1) # noise dimension: (num_samples, 1) def generate_data(num_samples, w…

汽车长翅膀:GPU 是如何加速深度学习模型的训练和推理过程的?

编者按&#xff1a;深度学习的飞速发展离不开硬件技术的突破&#xff0c;而 GPU 的崛起无疑是其中最大的推力之一。但你是否曾好奇过&#xff0c;为何一行简单的“.to(‘cuda’)”代码就能让模型的训练速度突飞猛进&#xff1f;本文正是为解答这个疑问而作。 作者以独特的视角&…

数仓架构解析(第45天)

系列文章目录 经典数仓架构传统离线大数据架构 文章目录 系列文章目录烂橙子-终生成长群群主前言1. 经典数仓架构2. 传统离线大数据架构 烂橙子-终生成长群群主 前言 经典数仓架构 传统离线大数据架构 背景解析 1. 经典数仓架构 1991年&#xff0c;比尔恩门&#xff08;Bill…

Python 中 Caffe 库的使用方法

Caffe 是一个由伯克利视觉与学习中心 (Berkeley Vision and Learning Center, BVLC) 开发的深度学习框架。它特别适用于图像分类和图像分割任务。以下是一个关于如何使用 Caffe 库的详细指南&#xff0c;包括安装、配置、构建和训练模型的步骤。 1. 安装 Caffe 安装 Caffe 可以…

Pinokio:一键安装开源 AI 应用

整合了几乎所有市面上开源的 AI 工具傻瓜式地一键安装AI 工具支持全平台&#xff1a;Windows、Mac、Linux官网&#xff1a;https://pinokio.computer项目仓库&#xff1a;GitHub - pinokiocomputer/pinokio: AI Browser文章地址&#xff1a;https://blog.i68.ltd/archives/Pino…

牛客算法题解:数字统计、两个数组的交集、点击消除

目录 BC153 [NOIP2010]数字统计 ▐ 题解 NC313 两个数组的交集 ▐ 题解 AB5 点击消除 ▐ 题解 BC153 [NOIP2010]数字统计 题目描述&#xff1a; 题目链接&#xff1a; [NOIP2010]数字统计_牛客题霸_牛客网 (nowcoder.com) ▐ 题解 题目要求统计出某段数组中一共有多少个…

关于Buffer和Channel的注意事项和细节

1.举例 package org.example.demo;import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.util.RandomAccess;/*** MappedByteBuffer可…

Linux入门级常用命令行(二)

目录 1、mv指令 2、rm指令 3、通配符* 4、chmod指令 5、tar指令 1、mv指令 功能 用于移动或重命名文件和目录的命令 基本用法 mv [选项] 源文件或目录 目标文件或目录 常用选项 -i&#xff1a;在覆盖文件之前提示用户确认。-f&#xff1a;强制移动或重命名&#xff0…

动量参数(Momentum Parameter)

动量参数&#xff08;Momentum Parameter&#xff09;在机器学习中指的是一种用于加速梯度下降算法的技术&#xff0c;特别是深度学习中优化神经网络权重时。简单来说&#xff0c;动量参数是一种帮助优化过程加速并减少震荡的技术。 具体来说&#xff0c;动量参数具有以下特点…

网络编程——wireshark抓包、tcp粘包

目录 一、前言 1.1 什么是粘包 1.2 为什么UDP不会粘包 二、编写程序 文件树 客户端程序 服务器程序 tcp程序 头文件 makefile 三、 实验现象 四、改进实验 五、小作业 一、前言 最近在做网络芯片的驱动&#xff0c;验证功能的时候需要借助wireshark这个工具&…

猫头虎分享:Numpy知识点一文带你详细学习np.random.randn()

&#x1f42f; 猫头虎分享&#xff1a;Numpy知识点一文带你详细学习np.random.randn() 摘要 Numpy 是数据科学和机器学习领域中不可或缺的工具。在本篇文章中&#xff0c;我们将深入探讨 np.random.randn()&#xff0c;一个用于生成标准正态分布的强大函数。通过详细的代码示…

Android Studio 一键删除 Recent Projects信息的方法

Android Studio打开项目多了就一堆最近项目的记录&#xff0c;在IDE里面只能一个个手动删除。 File - Recent Projects 解决方案&#xff1a;修改配置文件 Note&#xff1a;方法不唯一。 Android Studio 存储了一个包含最近打开项目信息的配置文件。通过手动编辑或删除recentP…

会员管理系统需求文档示例

1. 引言 目的&#xff1a; 本需求文档旨在明确会员管理系统的目标、功能和非功能性需求&#xff0c;以指导系统的设计、开发和测试过程。 背景&#xff1a; 随着公司业务的不断增长&#xff0c;我们需要一个高效、可靠的会员管理系统来帮助我们更好地管理客户关系、提高服务质…

科普文:kubernets原理

kubernetes 已经成为容器编排领域的王者&#xff0c;它是基于容器的集群编排引擎&#xff0c;具备扩展集群、滚动升级回滚、弹性伸缩、自动治愈、服务发现等多种特性能力。 本文将带着大家快速了解 kubernetes &#xff0c;了解我们谈论 kubernetes 都是在谈论什么。 一、背…

详细介绍BIO、NIO、IO多路复用(select、poll、epoll)

BIO、NIO、IO多路复用 BIO(Blocking IO)NIO(Non-blocking IO) 同步非阻塞IOIO多路复用selectpollepoll Redis的IO多路复用 BIO(Blocking IO) 最基础的IO模型&#xff0c;当进行IO操作时&#xff0c;线程会被阻塞&#xff0c;直到操作完成。 比如read和write&#xff0c;通常IO…

Python的输入规则

Python的输入特别有意思&#xff0c;它和C的输入不一样&#xff0c;它的输入的原型是类似于C的string类型&#xff0c;但是对于一些有意思的算法题来说&#xff0c;光是读入string型的内容并不容易解题&#xff0c;于是我们可以从两个方面来将输入给转化。 1. 先使用函数input…

SGLang 大模型推理框架 qwen2部署使用案例;openai接口调用、requests调用

参考: https://github.com/sgl-project/sglang 纯python写,号称比vllm、tensorRT还快 暂时支持模型 安装 可以pip、源码、docker安装,这里用的pip 注意flashinfer安装最新版,不然会可能出错误ImportError: cannot import name ‘top_k_top_p_sampling_from_probs’ fr…

ConcurrentHashMap 和 Hashtable 的区别

ConcurrentHashMap 概念 ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同JDK1.7的 ConcurrentHashMap 底层采用 分段的数组链表 实现JDK1.8 采用的数据结构是数组链表红黑二叉树在JDK1.7的时候&#xff0c;ConcurrentHashMap&#xff08;分段锁&…

EtherNet/IP转Profinet协议网关(经典配置案例)

怎么样才能把EtherNet/IP和Profinet网络连接起来呢?这几天有几个朋友问到了这个问题&#xff0c;作者在这里统一为大家详细说明一下。其实有一个设备可以很轻松地解决这个问题&#xff0c;名为JM-PN-EIP&#xff0c;下面是详细介绍。 一&#xff0c;设备主要功能 1、捷米特J…

LLMs之Hallucinations :《Extrinsic Hallucinations in LLMs》翻译与解读

LLMs之Hallucinations &#xff1a;《Extrinsic Hallucinations in LLMs》翻译与解读 导读&#xff1a; >> 背景和痛点&#xff1a;LLMs中的幻觉指生成不真实、虚构(或捏造)、不一致或无意义的内容。这种现象被称为幻觉&#xff08;hallucination&#xff09;。这种现象可…