欢迎拜访:雾里看山-CSDN博客
本篇主题算法思想 之 拓扑排序问题
发布时间:2025.8.4
隶属专栏:算法

在这里插入图片描述

目录

  • 算法介绍
    • 核心原理
    • 适用场景
    • 实现步骤(Kahn 算法)
  • 例题
    • 课程表
      • 题目链接
      • 题目描述
      • 算法思路
      • 代码实现
    • 课程表 II
      • 题目链接
      • 题目描述
      • 算法思路
      • 代码实现
    • 火星词典
      • 题目链接
      • 题目描述
      • 算法思路
      • 代码实现

算法介绍

拓扑排序(Topological Sorting)是针对有向无环图(DAG, Directed Acyclic Graph) 的一种重要排序算法,其核心是将图中所有顶点按照一定规则排列,使得对于图中任意一条有向边 (u, v),顶点 u 都排在顶点 v 之前。

拓扑排序的本质是解决 “依赖关系” 问题—— 当任务 / 节点之间存在先后依赖(如 “必须完成 A 才能做 B”)时,通过拓扑排序可以找到一个合法的执行顺序;若图中存在环(如 “A 依赖 B,B 依赖 C,C 依赖 A”),则不存在拓扑排序(此时可用于检测环)。

核心原理

拓扑排序的关键是 “消除入度为 0 的节点”

  • 入度(In-degree):顶点被指向的边的数量(即 “前驱节点” 的数量)。
  • 入度为 0 的节点:没有前驱依赖,可优先执行 / 输出。
  • 流程:反复找到入度为 0 的节点,输出该节点后移除其所有出边(减少其邻接节点的入度),直至所有节点被输出(合法拓扑序)或无法找到入度为 0 的节点(存在环,无拓扑序)。

适用场景

拓扑排序广泛应用于 “存在依赖关系” 的场景:

  • 课程安排:修课需先完成先修课程(如 “修算法前必须修数据结构”)。
  • 任务调度:任务 A 必须在任务 B 之前执行。
  • 编译依赖:程序模块的编译顺序(被依赖的模块先编译)。
  • 依赖包安装:软件包的安装顺序(依赖的包先安装)。

实现步骤(Kahn 算法)

Kahn 算法是最直观的拓扑排序实现,利用队列存储 “入度为 0 的节点”,通过层次遍历逐步消除依赖。

步骤

  • 构建邻接表:存储图的边关系(u -> v 表示 uv 的前驱)。
  • 计算入度数组:记录每个节点的入度(初始值)。
  • 初始化队列:将所有入度为 0 的节点加入队列(这些节点无依赖,可优先输出)。
  • BFS 遍历:
    • 从队列中取出一个节点 u,加入拓扑序结果。
    • 遍历 u 的所有邻接节点 v,将 v 的入度减 1(因为 u 已被 “消除”,v 的一个依赖 被满足)。
    • v 的入度减为 0,将其加入队列(此时 v 已无依赖)。
  • 检查结果:若拓扑序的长度等于节点总数,说明是合法 DAG(存在拓扑序);否则图中存在环(无拓扑序)。

例题

课程表

题目链接

207. 课程表

题目描述

你这个学期必须选修 numCourses 门课程,记为 0numCourses - 1

在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi

  • 例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1


请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false

示例 1

输入:numCourses = 2, prerequisites = [[1,0]]
输出:true
解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。

示例 2

输入:numCourses = 2, prerequisites = [[1,0],[0,1]]
输出:false
解释:总共有 2 门课程。学习课程 1 之前,你需要先完成​课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。

提示

  • 1 <= numCourses <= 2000
  • 0 <= prerequisites.length <= 5000
  • prerequisites[i].length == 2
  • 0 <= ai, bi < numCourses
  • prerequisites[i] 中的所有课程对 互不相同

算法思路

标准拓扑排序问题,直接使用算法介绍中的实现步骤进行即可。

代码实现

class Solution {
public:bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {//1. 准备工作unordered_map<int, vector<int>> edges; // 邻接表存图vector<int> in(numCourses);// 保存每一个点的入度//2. 建图for(auto& e : prerequisites){int a = e[0], b = e[1];// b->aedges[b].push_back(a);in[a]++;}//3. 拓扑排序queue<int> q;// (1). 把所有入度为0的点存进去for(int i = 0; i < numCourses; i++){if(in[i] == 0)q.push(i);}// (2). bfswhile(q.size()){int n = q.front();q.pop();for(int a : edges[n]){in[a]--;if(in[a] == 0)q.push(a);}}// (3). 判断是否有环for(int i : in){if(i)return false;}return true;}
};

在这里插入图片描述

课程表 II

题目链接

210. 课程表 II

题目描述

现在你总共有 numCourses 门课需要选,记为 0numCourses - 1。给你一个数组 prerequisites ,其中 prerequisites[i] = [ai, bi] ,表示在选修课程 ai必须 先选修 bi

  • 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示:[0,1]


返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回 任意一种 就可以了。如果不可能完成所有课程,返回 一个空数组

示例 1

输入:numCourses = 2, prerequisites = [[1,0]]
输出:[0,1]
解释:总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1] 。

示例 2

输入:numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]
输出:[0,2,1,3]
解释:总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。
因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3] 。

示例 3

输入:numCourses = 1, prerequisites = []
输出:[0]

提示

  • 1 <= numCourses <= 2000
  • 0 <= prerequisites.length <= numCourses * (numCourses - 1)
  • prerequisites[i].length == 2
  • 0 <= ai, bi < numCourses
  • ai != bi
  • 所有[ai, bi] 互不相同

算法思路

整体思路和上一题一样,多一个数组保存结果即可

代码实现

class Solution {
public:vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {//1. 准备工作unordered_map<int, vector<int>> edges; // 邻接表存图vector<int> in(numCourses);// 保存每一个点的入度vector<int> ret;//2. 建图for(auto& e : prerequisites){int a = e[0], b = e[1];// b->aedges[b].push_back(a);in[a]++;}//3. 拓扑排序queue<int> q;// (1). 把所有入度为0的点存进去for(int i = 0; i < numCourses; i++){if(in[i] == 0)q.push(i);}// (2). bfswhile(q.size()){int n = q.front();q.pop();ret.push_back(n);for(int a : edges[n]){in[a]--;if(in[a] == 0)q.push(a);}}// (3). 判断是否有环for(int i : in){if(i)return vector<int>();}return ret;}
};

在这里插入图片描述

火星词典

题目链接

LCR 114. 火星词典

题目描述

现有一种使用英语字母的外星文语言,这门语言的字母顺序与英语顺序不同。

给定一个字符串列表 words ,作为这门语言的词典,words 中的字符串已经 按这门新语言的字母顺序进行了排序

请你根据该词典还原出此语言中已知的字母顺序,并 按字母递增顺序 排列。若不存在合法字母顺序,返回 "" 。若存在多种可能的合法字母顺序,返回其中 任意一种 顺序即可。

字符串 s 字典顺序小于 字符串 t 有两种情况:

  • 在第一个不同字母处,如果 s 中的字母在这门外星语言的字母顺序中位于 t 中字母之前,那么 s 的字典顺序小于 t
  • 如果前面 min(s.length, t.length) 字母都相同,那么 s.length < t.length 时,s 的字典顺序也小于 t


示例 1:

输入:words = [“wrt”,“wrf”,“er”,“ett”,“rftt”]
输出:“wertf”

示例 2

输入:words = [“z”,“x”]
输出:“zx”

示例 3

输入:words = [“z”,“x”,“z”]
输出:“”
解释:不存在合法字母顺序,因此返回 “”。

提示

  • 1 <= words.length <= 100
  • 1 <= words[i].length <= 100
  • words[i] 仅由小写英文字母组成

算法思路

  1. 先根据字典两两比较字母之间的先后顺序,建立有向无环图
    a. 两层 for 循环枚举出所有的两个字符串的组合;
    b. 然后利用指针,根据字典序规则找出信息。
  2. 根据有向无环图做拓扑排序找出字母的顺序

代码实现

class Solution {unordered_map<char, unordered_set<char>> edges;// 邻接表存图unordered_map<char,int> in;// 保存每一个点的入度bool check = false;
public:string alienOrder(vector<string>& words) {for(auto& s : words)for(auto& c : s)in[c] = 0;int n = words.size();for(int i = 0; i < n; i++){for(int j = i+1; j < n; j++){add(words[i],words[j]);if(check) return "";}}string ret;queue<char> q;for(auto& [a,b] : in){if(b == 0)q.push(a);}while(q.size()){char c = q.front();q.pop();ret += c;for(char ch : edges[c]){in[ch]--;if(in[ch] == 0)q.push(ch);}}for(auto& [a,b]:in)if(b != 0)return "";return ret;}void add(const string& s1, const string& s2){int n = min(s1.size(), s2.size());int i = 0;for(; i < n; i++){if(s1[i] != s2[i]) {char a = s1[i], b = s2[i];// a->b;if(!edges.count(a) || !edges[a].count(b)){edges[a].insert(b);in[b]++;}break;}}if(i==s2.size() && i < s1.size())check = true;}
};

在这里插入图片描述

⚠️ 写在最后:以上内容是我在学习以后得一些总结和概括,如有错误或者需要补充的地方欢迎各位大佬评论或者私信我交流!!!

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

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

相关文章

机器学习 入门——决策树分类

决策树是一种直观且强大的机器学习算法&#xff0c;适用于分类和回归任务。本文将全面介绍决策树分类的原理、实现、调优和实际应用。一、什么是决策树分类1.概念决策树分类是一种树形结构的分类模型&#xff0c;它通过递归地将数据集分割成更小的子集来构建决策规则。就像我们…

虚拟机中查看和修改文件权限

在虚拟机中管理文件权限是系统管理的重要部分&#xff0c;无论是在Linux还是Windows虚拟机中。下面我将详细介绍两种主要系统的权限管理方法。Linux虚拟机中的文件权限管理查看文件权限使用ls命令&#xff1a;ls -l 文件名输出示例&#xff1a;-rwxr-xr-- 1 user group 1024 Ju…

图像处理拉普拉斯算子

AI对话记录&#xff0c;还没有来得及仔细验证和推导&#xff0c;目前只是记录 当然可以&#xff01;我们来一步步推导拉普拉斯算子在旋转变换下保持不变的数学过程。这里以二维情况为例&#xff0c;最直观也最常见。&#x1f9ee; 拉普拉斯算子旋转不变性的推导&#xff08;二维…

React ahooks——副作用类hooks之useThrottleEffect

useThrottleEffect 是 ahooks 提供的节流版 useEffect&#xff0c;它在依赖项变化时执行副作用函数&#xff0c;但会限制执行频率。一、基本语法useThrottleEffect(effect: React.EffectCallback,deps?: React.DependencyList,options?: Options )二、参数详解2.1. effect (必…

【建模与仿真】融合画像约束和潜在特征的深度推荐算法

导读&#xff1a; 基于深度学习的推荐算法已成为推荐系统领域的研究趋势。然而&#xff0c;大多数现有工作仅考虑单一的用户与物品交互数据&#xff0c;限制了算法的预测性能。本文提出一种画像约束的编码方式&#xff0c;并融合隐因子模型中的潜在特征&#xff0c;丰富了推荐…

华为网路设备学习-26(BGP协议 二)路径属性

一、属性分类二、属性含义①公认必遵&#xff1a;所有BGP对等体 必须识别 且 在Update报文中携带1.Origin2.AS-Path3.Next hop②公认自决&#xff1a;所有BGP对等体 必须识别但可以不在Update报文中携带 1.Local-Preference2.ATOMIC_Aggregate③可选传递&#xff1a;所有BGP对…

从0搭建YOLO目标检测系统:实战项目+完整流程+界面开发(附源码)

文章目录一、前言二、专栏介绍三、已有系统介绍3.0 基于yolo通用目标检测系统&#xff08;手把手教你修改成为自己的检测系统&#xff09;3.1 基于yolov8柑橘检测系统3.2 基于yolov8舰船检测系统3.3 基于yolo11人脸检测系统3.4 基于yolov8无人机影像光伏板缺陷检测系统一、前言…

【测试】自动化测试工具基础知识及基本应用

下面详细介绍一些常用的自动化测试工具及其基本概念&#xff0c;并提供具体的示例代码&#xff0c;帮助你更好地理解和应用这些工具。1. 自动化测试的基本概念自动化测试是通过软件程序自动执行测试用例的过程。与手动测试相比&#xff0c;自动化测试能够提高测试效率、减少人为…

ArcGIS的字段计算器生成随机数

在ArcGIS的字段计算器中使用Python脚本生成0-100的随机数&#xff0c;可以按照以下步骤操作&#xff1a; 打开属性表&#xff0c;选择要计算的字段打开字段计算器选择"Python"解析器勾选"显示代码块"在"预逻辑脚本代码"中输入以下代码在下方表达…

【前端:Html】--1.1.基础语法

目录 1.HTML--简介 2.HTML--编译器 步骤一:启动记事本 步骤二:用记事本来编辑 HTML 步骤三:保存 HTML 步骤四:在浏览器中运行 HTML 3.HTML--基础 3.1.HTML声明--!DOCTYPE 3.2.HTML 标题--h1 3.3.HTML 段落--p 3.3.1. 水平线--hr 3.3.2.换行符--br 3.3.3.固定格式…

FreeSWITCH 简单图形化界面46 - 收集打包的一些ASR服务

FreeSWITCH 简单图形化界面46 - 收集打包的一些ASR服务 0、一个fs的web配置界面预览1、docker地址2、使用2.1 下载2.2 运行 3、例子3.1 下载3.2 启动3.3 编译mod_audio_fork或者mod_audio_stream模块使用3.4 编写呼叫路由和呼叫脚本呼叫路由呼叫脚本 3.5 esl捕获识别结果3.6 其…

20250805问答课题-实现TextRank + 问题分类

textRank的工具包实现其他可能的实现方法&#xff0c;对比结果查找分类的相关算法 目录 1. 关键词提取TF-IDF TextRank 1.1. TF-IDF算法 1.2. TextRank算法 1.3. 双算法提取关键词 2. 问题分类 2.1. 预处理 2.2. 获取BERT向量 2.3. 一级标签预测 2.4. 二级标签预测 3…

Memcached缓存与Redis缓存的区别、优缺点和适用场景

一、核心差异概述特性MemcachedRedis​数据结构​简单键值存储丰富数据结构&#xff08;String/Hash/List/Set等&#xff09;​持久化​不支持支持RDB和AOF两种方式​线程模型​多线程单线程&#xff08;6.0支持多线程I/O&#xff09;​内存管理​Slab分配LRU淘汰多种淘汰策略&…

Git简易教程

Git教程 VCS Version Control System版本控制系统 配置用户名邮箱 配置用户名和邮箱 git config --global user.name mihu git config --global user.email aaabbb.com初始化仓库 从项目仓库拉 git clone [项目地址]新建文件夹之后 git init提交操作 提交到仓库 git add . #把…

关于Web前端安全之XSS攻击防御增强方法

仅依赖前端验证是无法完全防止 XSS的&#xff0c;还需要增强后端验证&#xff0c;使用DOMPurify净化 HTML 时&#xff0c;还需要平衡安全性与业务需求。一、仅依赖前端验证无法完全防止 XSS 的原因及后端验证的重要性1. 前端验证的局限性前端验证&#xff08;如 JavaScript 输入…

消息系统技术文档

消息系统技术文档 概述 本文档详细说明了如何在现有的LHD通信系统中添加自己的消息类型&#xff0c;包括消息的发送、接收、解析和处理的完整流程。 系统架构 消息流程架构图 #mermaid-svg-My7ThVxSl6aftvWK {font-family:"trebuchet ms",verdana,arial,sans-serif;f…

【NLP舆情分析】基于python微博舆情分析可视化系统(flask+pandas+echarts) 视频教程 - 微博舆情数据可视化分析-热词情感趋势树形图

大家好&#xff0c;我是java1234_小锋老师&#xff0c;最近写了一套【NLP舆情分析】基于python微博舆情分析可视化系统(flaskpandasecharts)视频教程&#xff0c;持续更新中&#xff0c;计划月底更新完&#xff0c;感谢支持。今天讲解微博舆情数据可视化分析-热词情感趋势树形图…

8月4日 强对流天气蓝色预警持续:多地需警惕雷暴大风与短时强降水

中央气象台8月4日10时继续发布强对流天气蓝色预警,提醒广大民众注意防范即将到来的恶劣天气。 预警详情: 时间范围: 8月4日14时至5日14时 影响区域: 雷暴大风或冰雹: 西北地区中东部、华北中北部、华南南部等地,风力可达8级以上。 短时强降水: 西北地区中东部、华北、…

C语言数据结构(4)单链表专题2.单链表的应用

1. 链表经典算法——OJ题目 1.1 单链表相关经典算法OJ题1&#xff1a;移除链表元素 1.2 单链表相关经典算法OJ题2&#xff1a;反转链表 1.3 单链表相关经典算法OJ题3&#xff1a;合并两个有序链表 1.4 单链表相关经典算法OJ题4&#xff1a;链表的中间结点 1.5 循环链表…

Shell 脚本发送信号给 C 应用程序,让 C 应用程序回收线程资源后自行退出。

下面分别给出一个 Shell 脚本和 C 程序的例子&#xff0c;实现通过 Shell 脚本发送信号给 C 应用程序&#xff0c;让 C 应用程序回收线程资源后自行退出。原理在 Linux 系统中&#xff0c;我们可以使用信号机制来实现进程间的通信。Shell 脚本可以使用 kill 命令向指定的进程发…