【算法学习】拓扑排序(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.rhkb.cn/news/15921.html

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

相关文章

JavaEE-前端与后台的搭建

一.idea连接数据库 在使用 IntelliJ IDEA 连接数据库时&#xff0c;可以按照以下步骤操作&#xff1a; ### 1. 打开数据库工具窗口 - 在 IntelliJ IDEA 中&#xff0c;点击右侧的 Database 工具窗口&#xff0c;或通过 View -> Tool Windows -> Database 打开。 ### 2. 添…

华为Mate 70 Pro或推出全新版本

关于华为Mate 70 Pro或推出全新版本的相关内容&#xff1a;可能的版本及命名。 据数码博主“定焦数码”爆料&#xff0c;华为Mate 70 Pro将推出新版本&#xff0c;命名为“优享版”。这一命名方式与华为Mate 60系列中的Mate 60 Pro乐臻版类似&#xff0c;预计优享版也会是一个组…

Linux 实操篇 实用指令

一、远程登录到Linux服务器 &#xff08;1&#xff09;为什么需要远程登录Linux linux服务器是开发小组共享的正式上线的项目是运行在公网因此程序员需要远程登陆到Linux进行项目管理或者开发画出简单的网络拓扑示意图远程登陆客户端有Xshell6&#xff0c;Xftp6&#xff0c;我…

SpringBoot 统一功能处理之拦截器、数据返回格式、异常处理

目录 拦截器 一、什么是拦截器 二 拦截器的使用 三 拦截路径配置 四 拦截器的执行流程 统一数据返回格式 统一异常处理 拦截器 一、什么是拦截器 拦截器是Spring框架提供的核心功能之一&#xff0c;主要用来拦截用户的请求&#xff0c;在指定方法前后&#xff0c;根据业务…

Django学习笔记(第一天:Django基本知识简介与启动)

博主毕业已经工作一年多了&#xff0c;最基本的测试工作已经完全掌握。一方面为了解决当前公司没有自动化测试平台的痛点&#xff0c;另一方面为了向更高级的测试架构师转型&#xff0c;于是重温Django的知识&#xff0c;用于后期搭建测试自动化平台。 为什么不选择Java&#x…

Spring Cloud工程完善

目录 完善订单服务 启动类 配置文件 实体类 Controller Service Mapper 测试运行 完成商品服务 启动类 配置文件 实体类 Controller Service Mapper 测试运行 远程调用 需求 实现 1.定义RestTemplate 2.修改order-service中的OrderService 测试运行 Rest…

如何将网站提交百度收录完整SEO教程

百度收录是中文网站获取流量的重要渠道。本文以我的网站&#xff0c;www.mnxz.fun&#xff08;当然现在没啥流量&#xff09; 为例&#xff0c;详细讲解从提交收录到自动化维护的全流程。 一、百度收录提交方法 1. 验证网站所有权 1、登录百度搜索资源平台 2、选择「用户中心…

Linux ftrace 内核跟踪入门

文章目录 ftrace介绍开启ftrace常用ftrace跟踪器ftrace使用ftrace跟踪指定内核函数ftrace跟踪指定pid ftrace原理ftrace与stracetrace-cmd 工具KernelShark参考 ftrace介绍 Ftrace is an internal tracer designed to help out developers and designers of systems to find wh…

VUE项目中实现权限控制,菜单权限,按钮权限,接口权限,路由权限,操作权限,数据权限实现

VUE项目中实现权限控制&#xff0c;菜单权限&#xff0c;按钮权限&#xff0c;接口权限&#xff0c;路由权限&#xff0c;操作权限&#xff0c;数据权限实现 权限系统分类&#xff08;RBAC&#xff09;引言菜单权限按钮权限接口权限路由权限 菜单权限方案方案一&#xff1a;菜单…

Pdf手册阅读(1)--数字签名篇

原文阅读摘要 PDF支持的数字签名&#xff0c; 不仅仅是公私钥签名&#xff0c;还可以是指纹、手写、虹膜等生物识别签名。PDF签名的计算方式&#xff0c;可以基于字节范围进行计算&#xff0c;也可以基于Pdf 对象&#xff08;pdf object&#xff09;进行计算。 PDF文件可能包…

CSS3+动画

浏览器内核以及其前缀 css标准中各个属性都要经历从草案到推荐的过程&#xff0c;css3中的属性进展都不一样&#xff0c;浏览器厂商在标准尚未明确的情况下提前支持会有风险&#xff0c;浏览器厂商对新属性的支持情况也不同&#xff0c;所有会加厂商前缀加以区分。如果某个属性…

微信小程序分包异步化

分包1引入分包2的组件或者js 引入组件&#xff1a; 主包里的pages/tabbars/tabbar1/tabbar1页面 引入分包sub1的sub1/components/sub1-component/sub1-component组件 1、分包预下载 首先在app.js定义preloadRule "preloadRule": {"pages/tabbars/tabbar1/tabb…

后端java工程师经验之谈,工作7年,mysql使用心得

mysql 工作7年&#xff0c;mysql使用心得 mysql1.创建变量2.创建存储过程2.1&#xff1a;WHILE循环2.2&#xff1a;repeat循环2.3&#xff1a;loop循环2.4&#xff1a;存储过程&#xff0c;游标2.5&#xff1a;存储过程&#xff0c;有输入参数和输出参数 3.三种注释写法4.case …

基于 GEE 利用插值方法填补缺失影像

目录 1 完整代码 2 运行结果 利用GEE合成NDVI时&#xff0c;如果研究区较大&#xff0c;一个月的影像覆盖不了整个研究区&#xff0c;就会有缺失的地方&#xff0c;还有就是去云之后&#xff0c;有云量的地区变成空值。 所以今天来用一种插值的方法来填补缺失的影像&#xf…

unity学习34:角色相关3,触发器trigger,铰链 hingejoint 等 spring joint, fixed joint

目录 1 触发的实现条件 1.1 碰撞的的实现条件 1.2 触发的实现条件 1.3 触发器trigger&#xff0c;直接拿 碰撞器collider修改下配置即可 2 触发器相关实验&#xff1a;触发开门效果 2.0 目标 2.1 player物体的属性 2.2 新建一个trigger 物体 2.3 新建一个被trigger 控…

(1/100)每日小游戏平台系列

每日小游戏平台 项目简介以及地址 准备开发一个一百天小游戏平台&#xff0c;使用Flask构建的简单游戏导航网站&#xff0c;无需登录&#xff0c;让大家在返工的同时也可以愉快的摸鱼玩耍。 每天更新一个小游戏上传&#xff0c;看看能不能坚持一百天。 这些小游戏主要使用前端…

从零到一:基于Rook构建云原生Ceph存储的全面指南(上)

文章目录 一.Rook简介二.Rook与Ceph架构2.1 Rook结构体系2.2 Rook包含组件1&#xff09;Rook Operator2&#xff09;Rook Discover3&#xff09;Rook Agent 2.3 Rook与kubernetes结合的架构图如下2.4 ceph特点2.5 ceph架构2.6 ceph组件 三.Rook部署Ceph集群3.1 部署条件3.3 获取…

第40天:Web开发-JS应用VueJS框架Vite构建启动打包渲染XSS源码泄露代码审计

#知识点 1、安全开发-VueJS-搭建启动&打包安全 2、安全开发-VueJS-源码泄漏&代码审计 一、Vue搭建创建项目启动项目 1、Vue 框架搭建->基于nodejs搭建&#xff0c;安装nodejs即可 参考&#xff1a;https://cn.vuejs.org/ 已安装18.3或更高版本的Node.js 2、Vue 创建…

DeepSeek做赛车游戏

赛车模型 2D生成图片 任意AI图片软件SD&#xff0c;MJ 图片生成3D模型 车身 车轮 场景 Rodin,Tripo和Meshy 询问deepSeek如何开发 拷贝代码 将汽车运行代码拖到汽车上 再让AI写个摄像头跟随代码 再去提问deepseek控制轮胎和一些处理细节

软考高级《系统架构设计师》知识点(一)

计算机硬件 校验码 码距&#xff1a;就单个编码A:00而言&#xff0c;其码距为1&#xff0c;因为其只需要改变一位就变成另一个编码。在两个编码中&#xff0c;从A码到B码转换所需要改变的位数称为码距&#xff0c;如A:00要转换为B:11&#xff0c;码距为2。一般来说&#xff0c;…