m数据结构 day16 图(六)拓扑排序:为有向无环图构造拓扑序列

2024-03-08 09:30

本文主要是介绍m数据结构 day16 图(六)拓扑排序:为有向无环图构造拓扑序列,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 拓扑排序
    • 基础
      • 主角:有向无环图 DAG
      • AOV网 VS AOE网
        • AOV网(点表示活动,弧表示活动之间的制约关系)
        • AOE网(弧表示活动,弧的权值表示活动的持续时间,点表示事件)
      • 拓扑排序判断网是否有环
    • 算法1:khan算法,思路好理解, O ( n + e ) O(n+e) O(n+e)
      • 代码
        • 边结点结构,顶点结点结构,邻接表结构
        • 拓扑排序,要用栈来存储入度为0的点
    • 算法2:基于DFS,代码好写
      • 代码

拓扑排序

拓扑排序是很重要的,对于DAG有很大的应用价值。

基础

主角:有向无环图 DAG

有向无环图常常应用于工程规划中,通过拓扑排序可以有效分析出一个图中是否有环,如果不存在,那么他的拓扑序列是什么。

看了B站几个视频,大致了解了一点,现在开始自己看书,自己思考,自己总结。

拓扑排序要做的事情:把有向无环图的所有顶点排成一条线性序列,即拓扑序列。拓扑序列需要满足的唯一条件就是:只要v到w能走通,那v就必须排在w的前面。

拓扑序列不唯一
在这里插入图片描述

AOV网 VS AOE网

所有讲拓扑排序的视频和书都要提到这两个名词,所以这两个词很重要,还是要好好理解一下。

我觉得这两种网络就是就是两种特殊的图而已,特殊在于他们都是对工程建模,是为了适应某种工程应用需求而产生的,所以让顶点,边,权表示和普通网图不一样的含义,对他们使用的算法(比如对AOV拓扑排序,对AOE找关键路径)也和普通网图不一样,但这些不一样都是为了去迎合他们背后的应用需求,所以给他们起个特殊名字也并不难理解,因为他们也具有了一定通用性,塑造了一个不可忽视的类型。

AOV网(点表示活动,弧表示活动之间的制约关系)

AOV网,activity on vertex network, 一般用于表示工程的流程。即用顶点表示活动的有向网,而弧表示活动之间的制约关系,主要是活动执行的先后顺序。AOV网中不能有回路,因为你不能要求某个活动的执行要以自己的执行为先决条件。

AOE网(弧表示活动,弧的权值表示活动的持续时间,点表示事件)

AOE网,activity on edge network,用弧表示活动的有向网,弧的权值表示活动的持续时间,用顶点表示事件

AOE网中没有入边的顶点叫做始点或者源点,没有出边的顶点称为终点或者汇点

正常情况下,AOE网只有一个源点和一个汇点。因为一个工程只有一个开始和一个结束。比如:

在这里插入图片描述

拓扑排序判断网是否有环

如果拓扑排序能把网的所有顶点都输出,则无环;否则有环,且剩下的顶点们就是环的顶点(可能不止一个环)。就算只少了一个顶点也是有环,即少的那个顶点的自环。

算法1:khan算法,思路好理解, O ( n + e ) O(n+e) O(n+e)

拓扑排序的实现算法不止一种,常见的有两个,一个是khan算法,一个是基于深度优先搜索的,各有特点,khan算法适合考试,因为容易理解,容易做题,但是计算机上代码相对复杂点。而基于DFS的,则理解起来难一点,因为是逆序,但是代码极其简单,所以工程中用的更多。

khan算法:
在这里插入图片描述

这个思路,需要删除顶点以及顶点的出边,所以邻接表存储结构很适合,但是又要知道每个顶点的入度是否为0,所以需要给邻接表的结点结构增加一个数据域,存储入度。

在这里插入图片描述

比如,下面这个图:
在这里插入图片描述
转换为邻接表存储结构为:
在这里插入图片描述

代码

边结点结构,顶点结点结构,邻接表结构
typedef struct EdgeNode
{int adjvex;//弧头顶点的下标int weight;//权值struct EdgeNode * nextEdge;
}EdgeNode;typedef struct VertexNode
{int in;//入度int data;EdgeNode * firstEdge;
}VertexNode, AdjList[MAXVEX];typedef struct
{AdjList adjlist;int numV, numE;
}GraphAdjList;
拓扑排序,要用栈来存储入度为0的点

当然用队列也是可以的,但是这里用数组做一个动态的栈最方便,记得函数最后一定要释放内存哦

/*如果图G没有环,则输出拓扑序列并返回true,否则返回false*/
bool TopologicalSort(GraphAdjList * G)
{int count = 0;//已输出顶点的个数int * stack;//栈,存储入度为0的顶点int top = -1;//指向栈顶stack = (int *)malloc(G->numV * sizeof(int));//动态分配内存,避免宏定义值过大带来或过小的问题int i;//遍历所有顶点,把入度为0的顶点的在顶点表数组的下标入栈for (i=0;i<G->numV;++i)if (G->adjlist[i].in == 0)stack[++top] = i;int gettop;EdgeNode * e;while (top!=-1){gettop = stack[top--];//取出栈顶元素printf("%d -> ", G->adjlist[gettop].data);//打印这个元素,即加入拓扑序列++count;//遍历此顶点的边表,把边表的每个结点的顶点的入度减1(相当于删去了第gettop个点和他的出边)int k;for(e=G->adjlist[gettop].firstEdge;e!=NULL;e=e->nextEdge){k = e->adjvex;if (!(--G->adjlist[k].in))//如果这些顶点入度更新为0则也入栈stack[++top] = k;}}free(stack);if (count < G->numV)return false;return true;
}

时间复杂度分析:

不考虑有环的图,因为无环图需要的操作更多。第一个for把所有入度为0的点入栈, O ( n ) O(n) O(n);而后面的while循环里,每一个点出栈,都要执行他的出度边数目的操作次数,所有点都要出栈,所以总共的操作系数是总的边数,即e,所以是 O ( e ) O(e) O(e).

所以总的是 O ( n + e ) O(n+e) O(n+e)

下面的C++实现,来自这个博文,也很好,用了vector

//Kahn算法,关键在于需要维护一个入度为0的顶点的集合
int n,m;
int inDeg[N];  //i点的入度
vector<int>vec[N];  //i点的邻接表,即i与哪些点相连
int ans[N];    //排序结果数组int topSort()    //返回值代表是否有环,排序结果在ans数组
{int cnt=0;queue<int>q;  //如果需要相同优先级的顶点,序号小的先输出,则可建立优先队列,即//priority_queue<int,vector<int>,greater<int> >q;while(!q.empty())q.pop();for(int i=1;i<=n;i++)if(inDeg[i]==0)q.push(i);while(!q.empty()){int now = q.front();q.pop();ans[++cnt]=now;     //个数+1并存数组int len = vec[now].size();for(int i=0;i<len;i++){inDeg[vec[now][i]]--;if(inDeg[vec[now][i]]==0)q.push(vec[now][i]);}}return (cnt==n)?1:0;  //是否等于n,等于n代表无环
}void init()  //输入数据,n个点,m条边
{int x,y;cin>>n>>m;for(int i=1;i<=n;i++)vec[i].clear();memset(inDeg,0,sizeof(inDeg));for(int i=1;i<=m;i++){cin>>x>>y;inDeg[y]++;vec[x].push_back(y);}
}

算法2:基于DFS,代码好写

代码

也来自这个博文

//dfs实现,从出度的角度来构造,递归到最后返回
int n,m;
vector<int>vec[N];  //i点的邻接表,即i与哪些点相连
int ans[N],cnt;    //排序结果数组,cnt记录个数
int parent[N]; //记录其前置节点
//int d[N], Time, f[N]; //可以不要,时间time初始化为0,d[]记录第一次被发现时,f[]记录结束检查时
int vis[N];  //标记数组vis[i]=0表示还未访问过点i,c[i]=1表示已经访问过点i,并且还递归访问过它的所有子孙,c[i]=-1表示正在访问中,尚未返回bool dfs(int s)  //深度优先搜索(邻接表实现),记录时间戳,寻找最短路径
{vis[s]=-1;   //正在访问//Time++;d[s]=Time;int len = vec[s].size();for(int i=0;i<len;i++){int tmp=vec[s][i];if(vis[tmp]<0) //如果子孙比父亲先访问,说明存在有向环,失败退出return false;else if(!vis[tmp]&&!dfs(tmp)) //如果子孙未被访问,访问子孙返回假,说明也是失败return false;parent[tmp]=s;}vis[s]=1;//Time++;f[s]=Time;ans[cnt++]=s;  //结果是逆序的,递归的原因return true;
}bool topSort()    //返回值代表是否有环,排序结果在ans数组
{cnt=0;for(int i=1;i<=n;i++){parent[i]=-1;vis[i]=0;}//Time=0;for(int i=1;i<=n;i++)if(!vis[i])if(dfs(i)==false)return false;return true;
}void init()  //输入数据,n个点,m条边
{int x,y;for(int i=1;i<=n;i++)vec[i].clear();for(int i=1;i<=m;i++){cin>>x>>y;vec[x].push_back(y);}
}

这篇关于m数据结构 day16 图(六)拓扑排序:为有向无环图构造拓扑序列的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:https://blog.csdn.net/qq_36607894/article/details/106087435
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/786686

相关文章

利用Python实现时间序列动量策略

《利用Python实现时间序列动量策略》时间序列动量策略作为量化交易领域中最为持久且被深入研究的策略类型之一,其核心理念相对简明:对于显示上升趋势的资产建立多头头寸,对于呈现下降趋势的资产建立空头头寸... 目录引言传统策略面临的风险管理挑战波动率调整机制:实现风险标准化策略实施的技术细节波动率调整的战略价

Java List排序实例代码详解

《JavaList排序实例代码详解》:本文主要介绍JavaList排序的相关资料,Java排序方法包括自然排序、自定义排序、Lambda简化及多条件排序,实现灵活且代码简洁,文中通过代码介绍的... 目录一、自然排序二、自定义排序规则三、使用 Lambda 表达式简化 Comparator四、多条件排序五、

JAVA数组中五种常见排序方法整理汇总

《JAVA数组中五种常见排序方法整理汇总》本文给大家分享五种常用的Java数组排序方法整理,每种方法结合示例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录前言:法一:Arrays.sort()法二:冒泡排序法三:选择排序法四:反转排序法五:直接插入排序前言:几种常用的Java数组排序

PostgreSQL 序列(Sequence) 与 Oracle 序列对比差异分析

《PostgreSQL序列(Sequence)与Oracle序列对比差异分析》PostgreSQL和Oracle都提供了序列(Sequence)功能,但在实现细节和使用方式上存在一些重要差异,... 目录PostgreSQL 序列(Sequence) 与 oracle 序列对比一 基本语法对比1.1 创建序

Mybatis 传参与排序模糊查询功能实现

《Mybatis传参与排序模糊查询功能实现》:本文主要介绍Mybatis传参与排序模糊查询功能实现,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录一、#{ }和${ }传参的区别二、排序三、like查询四、数据库连接池五、mysql 开发企业规范一、#{ }和${ }传参的

C#数据结构之字符串(string)详解

《C#数据结构之字符串(string)详解》:本文主要介绍C#数据结构之字符串(string),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录转义字符序列字符串的创建字符串的声明null字符串与空字符串重复单字符字符串的构造字符串的属性和常用方法属性常用方法总结摘

C++从序列容器中删除元素的四种方法

《C++从序列容器中删除元素的四种方法》删除元素的方法在序列容器和关联容器之间是非常不同的,在序列容器中,vector和string是最常用的,但这里也会介绍deque和list以供全面了解,尽管在一... 目录一、简介二、移除给定位置的元素三、移除与某个值相等的元素3.1、序列容器vector、deque

C++快速排序超详细讲解

《C++快速排序超详细讲解》快速排序是一种高效的排序算法,通过分治法将数组划分为两部分,递归排序,直到整个数组有序,通过代码解析和示例,详细解释了快速排序的工作原理和实现过程,需要的朋友可以参考下... 目录一、快速排序原理二、快速排序标准代码三、代码解析四、使用while循环的快速排序1.代码代码1.由快

最长公共子序列问题的深度分析与Java实现方式

《最长公共子序列问题的深度分析与Java实现方式》本文详细介绍了最长公共子序列(LCS)问题,包括其概念、暴力解法、动态规划解法,并提供了Java代码实现,暴力解法虽然简单,但在大数据处理中效率较低,... 目录最长公共子序列问题概述问题理解与示例分析暴力解法思路与示例代码动态规划解法DP 表的构建与意义动

关于最长递增子序列问题概述

《关于最长递增子序列问题概述》本文详细介绍了最长递增子序列问题的定义及两种优化解法:贪心+二分查找和动态规划+状态压缩,贪心+二分查找时间复杂度为O(nlogn),通过维护一个有序的“尾巴”数组来高效... 一、最长递增子序列问题概述1. 问题定义给定一个整数序列,例如 nums = [10, 9, 2