【算法学习】拓扑排序(Topological Sorting)

目录

定义

例子

拓扑排序的实现

核心思想

 实现方法

1,Kahn算法(基于贪心策略)

步骤:

用二维数组存储图的例子 

 用哈希表存储图的例子

 2,基于DFS的后序遍历法

 总结

拓扑排序的应用场景

1,任务调度

2,课程安排

3,编译器优化

4,数据库查询优化


定义

拓扑排序是针对 有向无环图(DAG,Directed Acyclic Graph)的一种线性排序的算法。使得对于图中的每一条有向边u->v,节点u在排序中都出现在节点v之前。

例子

对于这个有向无环图,边有1->2,2->3,1->4,4->5,2->3。那么在拓扑排序中,1一定出现在2的前面,2一定出现在3的前面......。

拓扑排序的过程:每次选数时,都选择图中入度为0的节点,然后遍历该节点所连接的节点,将它们的入度减一,重复该过程,直到排序结果中包含所有节点,排序完成。

  • 如果图中存在环,比如1->2,2->3,3->1。在该图中没有入度为0的节点,无法选数。
  • 所以拓扑排序有一个重要的应用,判断有向图是否带环,如果不带环,则排序结果包含所有的节点;反之,该图带环。

拓扑排序的结果有这几种可能:

  • 1  2  3  4  5 
  • 1  4  2  3  5
  • 1  4  2  5  3
  • 1  2  4  5  3
  • 1  2  4  3  5

可以发现,拓扑排序的结果不是唯一的。

拓扑排序的实现

对拓扑排序可以总结如下:

核心思想

  • 目标:将图中的节点按依赖关系线性化,确保所有前驱节点优先于后继节点。

  • 适用条件:仅适用于无环的有向图(若图中有环,则无法完成拓扑排序)。

 实现方法

1,Kahn算法(基于贪心策略)

该算法也就是图中的广度优先搜索(BFS)算法。

步骤:

1,初始化

  • 统计图中所有节点的入度。
  • 将入度为0的节点加入队列中。

2,循环处理

  • 取出队列中的结果u,加入到排序结果中。
  • 遍历u所指向的节点v,将v的入度减1。
  • 若v的入度变为0,将v加入队列中。

3,终止条件

  • 若排序结果包含所有节点   ->成功
  • 若仍有节点未处理且队列为空   ->失败,图中有环

还有一个问题,就是如何来表示图,或者是存储图。我们可以用STL中的容器来抽象表示:

vector<vector<int>> 二维数组和unordered_map<int,vector<int>> 哈希表。这两种都可以用来存储int类型的,但如果节点是string类型的(如节点表示课程名称),使用unordered_map<string,vector<string>> 存图。

 

用二维数组存储图的例子 

#include <iostream>
#include <vector>
#include <queue>
using namespace std;bool TopSort(vector<vector<int>>& graph, int n, vector<int>& inDgree)
{int num = 0;                             //统计排序结果的数目queue<int> q;for (int i = 0; i < n; i++)				//将所有入度为0的节点放入队列中{if (inDgree[i] == 0)q.push(i);}while (q.size())						//循环处理{int u = q.front();                  //取队首q.pop();cout << u << " ";for (int v : graph[u])              //u->v{inDgree[v]--;                  //节点v入度减一if (inDgree[v] == 0)           //节点v入度为0则入队列q.push(v);}num++;}cout << endl;if (num == n)return true;elsereturn false;
}int main()
{int n, m;cout << "请输入顶点数和边数:";cin >> n >> m;vector<vector<int>> G(n);					//二维数组模拟邻接表存图for (int i = 0; i < m; i++){int x, y;cout << "请输入第" << i + 1 << "条边:" ;cin >> x >> y;G[x].push_back(y);}vector<int> inDgree(n);				//记录入度for (auto x : G){for (int y : x)					//节点指向  x->y{inDgree[y]++;               //y的入度++}}cout << "拓扑排序结果为:";bool ret = TopSort(G, n, inDgree);cout << ret << endl;return 0;
}

 用哈希表存储图的例子

#include <iostream>
#include <vector>
#include <queue>
#include <unordered_map>
using namespace std;vector<string> TopSort(unordered_map<string, vector<string>>& graph)
{unordered_map<string, int> inDgree;		//记录入度queue<string> q;vector<string> result;					//排序结果for (auto& [u,neighbors] : graph)        //初始化入度{if (!inDgree.count(u)) inDgree[u] = 0;  //确保所有节点被记录for (string v : neighbors){inDgree[v]++;}}for (auto& [node,degree] : inDgree)      //入度为0的入队列{if (degree == 0){q.push(node);}}//处理队列while (q.size()){string u = q.front();q.pop();result.push_back(u);for (auto& v : graph[u])             //u->v{inDgree[v]--;					 //入度--if (inDgree[v] == 0){q.push(v);					//入度为0,入队列}}}//检查环if (result.size() != inDgree.size())return {};return result;
}int main()
{//课程依赖关系    (u->v)表示u是v的先修课unordered_map<string, vector<string>> graph = {{"C1",{"C2","C3"}},{"C2",{"C4"}},{"C3",{"C4"}},{"C4",{}},{"C5",{"C4"}}};//拓扑排序vector<string> order = TopSort(graph);if (order.empty())cout << "图中存在环!" << endl;else{cout << "拓扑排序结果为:";for (string node : order){cout << node << " ";}}return 0;
}

 2,基于DFS的后序遍历法

步骤:

  1. 对每个未访问的节点执行DFS。

  2. 递归访问所有邻接节点。

  3. 将当前节点加入栈。

  4. 最终反转结果得到拓扑排序。

#include <iostream>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <string>
using namespace std;bool dfs(const string& u,unordered_map<string, vector<string>>& graph,unordered_set<string>& visited,unordered_set<string>& inStack,vector<string>& result)
{//该节点在访问路径中出现过,说明存在环if (inStack.count(u))  return false;//该节点已处理过,不再处理if (visited.count(u))  return true;inStack.insert(u);visited.insert(u);for (string v : graph[u]){if (!dfs(v, graph, visited, inStack, result))return false;}inStack.erase(u);result.push_back(u);return true;
}vector<string> TopSort(unordered_map<string, vector<string>>& graph)
{vector<string> result;  //记录结果unordered_set<string> inStack;  //记录访问路径 ,如果重复出现,说明存在环unordered_set<string> visited;  //记录访问过的节点for (auto& [u, _] : graph){if (!visited.count(u)){if (!dfs(u, graph, visited, inStack, result))return {};          //存在环}}return result;
}int main()
{//课程依赖关系    (u->v)表示u是v的先修课unordered_map<string, vector<string>> graph = {{"C1",{"C2","C3"}},{"C2",{"C4"}},{"C3",{"C4"}},{"C4",{}},{"C5",{"C4"}}};//拓扑排序vector<string> order = TopSort(graph);reverse(order.begin(), order.end());if (order.empty())cout << "图中存在环!" << endl;else{cout << "拓扑排序结果为:";for (string node : order){cout << node << " ";}}return 0;
}

 

 总结

  • 两种算法均能高效实现拓扑排序,时间复杂度均为O(V+E),V为顶点数,E为边数。
  • 若节点类型为int,可将unordered_map替换为vector提升性能。

拓扑排序的应用场景

1,任务调度

在项目管理中,任务之间可能存在依赖关系,某些任务必须在其他任务完成之后才能开始。通过拓扑排序,可以确定任务的执行顺序,确保每个任务在开始之前其前置任务已经完成。

2,课程安排

在教育领域,某些课程可能需要先修其他课程。通过拓扑排序,可以确定合理的课程修读顺序,避免循环依赖,帮助学生按照逻辑顺序学习课程内容。

3,编译器优化

在编译过程中,源代码的不同模块之间可能存在依赖关系。拓扑排序可以帮助确定模块的编译顺序,确保每个模块在被调用之前已经被正确编译。(如Makefile)

4,数据库查询优化

在数据库设计中,表与表之间可能存在外键约束。通过拓扑排序,可以决定表的插入或删除顺序,以避免违反外键约束。

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

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

相关文章

AGI时代的认知重塑:人类文明的范式转移与思维革命

文章目录 引言:站在文明转型的临界点一、认知危机:当机器开始理解世界1.1 AGI的本质突破:从模式识别到世界建模1.2 人类认知的脆弱性暴露二、认知革命:重构思维的四个维度2.1 元认知升级:从直觉思维到二阶观察2.2 混合智能:人机认知回路的构建2.3 认知安全:防御机器思维…

零基础学CocosCreator·第九季-网络游戏同步策略与ESC架构

课程里的版本好像是1.9&#xff0c;目前使用版本为3.8.3 开始~ 目录 状态同步帧同步帧同步客户端帧同步服务端ECS框架概念ECS的解释ECS的特点EntityComponentSystemWorld ECS实现逻辑帧&渲染帧 ECS框架使用帧同步&ECS 状态同步 一般游戏的同步策略有两种&#xff1a;…

实现限制同一个账号最多只能在3个客户端(有电脑、手机等)登录(附关键源码)

如上图&#xff0c;我的百度网盘已登录设备列表&#xff0c;有一个手机&#xff0c;2个windows客户端。手机设备有型号、最后登录时间、IP等。windows客户端信息有最后登录时间、操作系统类型、IP地址等。这些具体是如何实现的&#xff1f;下面分别给出android APP中采集手机信…

算法基础:贪心|双指针|二分|倍增

贪心 算法思想&#xff1a; 把整个问题分解成多个步骤&#xff0c;在每个步骤都选取当前步骤的最优方案&#xff0c;直到所有步骤结束&#xff1b;每个步骤都不会影响后续步骤。 核心&#xff1a;采取局部最优&#xff0c;最终结果就全局最优。 双指针 反向扫描 同向扫描 二…

在本地校验密码或弱口令 (windows)

# 0x00 背景 需求是验证服务器的弱口令&#xff0c;如果通过网络侧校验可能会造成账户锁定风险。在本地校验不会有锁定风险或频率限制。 # 0x01 实践 ## 1 使用 net use 命令 可以通过命令行使用 net use 命令来验证本地账户的密码。打开命令提示符&#xff08;CMD&#xff0…

【设计模式】【行为型模式】观察者模式(Observer)

&#x1f44b;hi&#xff0c;我不是一名外包公司的员工&#xff0c;也不会偷吃茶水间的零食&#xff0c;我的梦想是能写高端CRUD &#x1f525; 2025本人正在沉淀中… 博客更新速度 &#x1f4eb; 欢迎V&#xff1a; flzjcsg2&#xff0c;我们共同讨论Java深渊的奥秘 &#x1f…

OSPF高级特性(3):安全特效

引言 OSPF的基础我们已经结束学习了&#xff0c;接下来我们继续学习OSPF的高级特性。为了方便大家阅读&#xff0c;我会将高级特性的几篇链接放在末尾&#xff0c;所有链接都是站内的&#xff0c;大家点击即可阅读&#xff1a; OSPF基础&#xff08;1&#xff09;&#xff1a;工…

把 DeepSeek1.5b 部署在显卡小于4G的电脑上

这里写自定义目录标题 介绍准备安装 Ollama查看CUDA需要版本安装CudaToolkit检查Cuda是否装好二、设置Ollama环境变量三、验证是否跑在GPU上ollama如何导入本地下载的模型安装及配置docker安装open-webui启动open-webui开始对话介绍 Deepseek1.5b能够运行在只用cpu和gpu内存小…

WebSocket与Socket.io的区别

文章目录 引言一、WebSocket&#xff1a;原生的实时通信协议&#xff08;一&#xff09;WebSocket 是什么&#xff08;二&#xff09;WebSocket 的工作原理&#xff08;三&#xff09;WebSocket 的使用方法&#xff08;四&#xff09;WebSocket 的优势&#xff08;五&#xff0…

STM32 裸机 C编程 vs micropython编程 vs linux python

以led点亮为例。 STM32 裸机 C编程需要设置时钟&#xff0c;管脚。 static void MX_GPIO_Init(void) {GPIO_InitTypeDef GPIO_InitStruct {0};// GPIO端口时钟使能__HAL_RCC_GPIOA_CLK_ENABLE();// 配置PA5为推挽输出模式GPIO_InitStruct.Pin GPIO_PIN_5;GPIO_InitStruct.M…

AI语言模型的技术之争:DeepSeek与ChatGPT的架构与训练揭秘

云边有个稻草人-CSDN博客 目录 第一章&#xff1a;DeepSeek与ChatGPT的基础概述 1.1 DeepSeek简介 1.2 ChatGPT简介 第二章&#xff1a;模型架构对比 2.1 Transformer架构&#xff1a;核心相似性 2.2 模型规模与参数 第三章&#xff1a;训练方法与技术 3.1 预训练与微调…

稀土抑烟剂——为汽车火灾安全增添防线

一、稀土抑烟剂的基本概念 稀土抑烟剂是一类基于稀土元素&#xff08;如稀土氧化物和稀土金属化合物&#xff09;开发的高效阻燃材料。它可以显著提高汽车内饰材料的阻燃性能&#xff0c;减少火灾发生时有毒气体和烟雾的产生。稀土抑烟剂不仅能提升火灾时的安全性&#xff0c;…

硅基流动平台大模型 DeepSeek API 调用示例

硅基流动平台大模型 API 调用示例 硅基流动平台作为一个集成多种主流开源大模型的云服务平台&#xff0c;为用户提供了便捷的 API 调用方式&#xff0c;让用户无需自建硬件或进行复杂配置&#xff0c;即可轻松使用各种大模型。以下是详细的硅基流动平台大模型 API 调用示例&am…

vue项目 Axios创建拦截器

Axios 1. Axios 和 Ajax 简介2. Axios 和 Ajax 的区别3. 从 按钮 到 Axios请求后端接口的 大致顺序 1. Axios 和 Ajax 简介 Ajax&#xff08;Asynchronous JavaScript and XML&#xff09; 不是一种技术&#xff0c;而是一个编程技术概念&#xff0c;核心是通过 XMLHttpReques…

CNN-BiGRU卷积神经网络双向门控循环单元多变量多步预测,光伏功率预测

CNN-BiGRU卷积神经网络双向门控循环单元多变量多步预测&#xff0c;光伏功率预测 代码下载&#xff1a;CNN-BiGRU卷积神经网络双向门控循环单元多变量多步预测&#xff0c;光伏功率预测 一、引言 1.1、研究背景及意义 随着全球能源危机和环境问题的日益严重&#xff0c;可再…

碳纤维复合材料制造的六西格玛管理实践:破解高端制造良率困局的实战密码

碳纤维复合材料制造的六西格玛管理实践&#xff1a;破解高端制造良率困局的实战密码 在全球碳中和与高端制造升级的双重驱动下&#xff0c;碳纤维复合材料行业正经历前爆发式增长。航空航天、新能源汽车、风电叶片等领域对碳纤维产品的性能稳定性提出近乎苛刻的要求&#xff0…

如何保证Redis和MySQL数据的一致性刨析

1、常见的缓存更新策略&#xff1a; 定义&#xff1a;主要用来进行redis和mysql的数据同步更新的一些策略 内存淘汰&#xff1a;等触发淘汰机制后&#xff0c;刚好淘汰到了用户查询的数据&#xff0c;此时是null&#xff0c;会进行查询数据库并写入到缓存中&#xff0c;此时…

Golang GORM系列:GORM 高级查询教程

有效的数据检索是任何程序功能的基础。健壮的Go对象关系映射包&#xff08;称为GORM&#xff09;除了标准的CRUD操作之外&#xff0c;还提供了复杂的查询功能。这是学习如何使用GORM进行高级查询的综合资源。我们将涵盖WHERE条件、连接、关联、预加载相关数据&#xff0c;甚至涉…

协议-LVDS

是什么&#xff1f; LVDS 全称为 Low-Voltage Differential Signaling&#xff0c;低电压差分信号 低功耗、低误码率、低串扰和低辐射的差分信号&#xff0c;采用-350mV~350mV极底的电压摆幅高速差动传输数据&#xff0c;实现点对点或一点对多点的连接 由于电压幅度低&#xf…

dma_ddr 的编写 通过mig控制ddr3

此外还有别的模块 本模块是 其中一个 timescale 1ns/1ps module dma_ctrl (input wire ui_clk , //100MHZ 用户时钟input wire ui_rst_n ,//写fifo的写端口 input wire wf_wr_clk , //由数据产生模块的时…