图解Topo拓扑排序
往期文章目录
【干货满满!】图解Dijkstra迪杰斯特拉
【上篇文章!】手撕Floyd弗洛伊德
目录
往期文章目录
前言
一、Topo排序不是排序
二、AOV网和Topo序列
1.AOV网的概念
2.Topo序列的概念和思想
三、topo排序的代码实现
总结
前言
无论是什么程序都要和数据打交道,一个好的程序员会选择更优的数据结构来更好的解决问题,因此数据结构的重要性不言而喻。数据结构的学习本质上是让我们能见到很多前辈在解决一些要求时间和空间的难点问题上设计出的一系列解决方法,我们可以在今后借鉴这些方法,也可以根据这些方法在遇到具体的新问题时提出自己的解决方法。(所以各种定义等字眼就不用过度深究啦,每个人的表达方式不一样而已),在此以下的所有代码都是仅供参考,并不是唯一的答案,只要逻辑上能行的通,写出来的代码能达到相同的结果,并且在复杂度上差不多,就行了。
一、Topo排序不是排序
首先我们要知道:topo排序并不是真的排序!topo排序是针对于有向无环图而言,搞出一个带有路径(前后)关系的一个序列,而这个序列就叫做topo序列;
二、AOV网和Topo序列
1.AOV网的概念
在一个表示工程的有向图中顶点表示活动,而边表示活动之间的优先级关系,这种图就叫AOV网 (记住这个V表示顶点嘛),所以AOV网是针对于顶点的,而不是针对于边(在图中只有两种关系:针对于顶点和针对于边);
2.Topo序列的概念和思想
- 具有n个顶点的有向图,只要满足 Vi 到 Vj 有路径并且 Vi 要在 Vj 之前,对于这种顶点序列就叫做topo序列;
- topo排序算法思想:在有向图中选一个没有前驱的节点,输出(放入序列中),从图中删除顶点和以它为尾的弧;
这是什么意思呢,我们以如下有向无环图为例:
假设我们以出度为弧尾,那么根据上述topo排序的思想,我们要先找一个没有前驱的节点(也就是没有入度的节点),那么我们可以很容易的得到 V1 和 V5 都是没有前驱的节点,那我们先输出V1,并在图中删去 V1 以及以 V1 为尾的弧,如下图所示;
接着我们输出V5,在图中删去 V5 以及以 V5 为尾的弧,如下图所示;
这个时候我们发现 V3 和 V4 也成了一个没有前驱的节点,那么我们就应该重复上述的步骤,输出V3 和 V4 并在图中删去与它们有关的顶点和弧;
输出V3:
输出V4:
这时候 V2 和 V6 也成了没有前驱的节点并且在图中也没有它们的出度的弧,那么只要输出它们即可;
- 所以经过如上的步骤,我们可以得到一个topo序列:{ V1 V5 V3 V4 V2 V6 };那么这就是topo排序的思想和实现topo序列的步骤;
- 那么这时候可能有人就要问了:这个topo排序有什么作用咧?每一个算法的作用其实和它本身的存在条件是分不开的,就像在上文我们说过:topo排序是针对于有向无环图的算法,那么如果我们构建的是一个有环图呢?那么是不是根据上述的步骤我们绝对会在某一步找不到一个没有前驱的节点,(因为这个图有环嘛),那么我们输出的顶点数量就一定会小于图中总顶点的数量,这样是不是就可以判断:一个图是不是无环图?这就是topo排序的一个作用;
三、topo排序的代码实现
在写图的代码之前,我们都要思考一个问题:用什么结构去存储这个图最方便?很明显在这里我们更加侧重的是统计一个顶点的入度的数量,那么我们就应该用邻接表(邻接表的文章)去构建图最方便;
#define MAX_VERTEX_NUM 20//最大顶点个数
#define VertexType char//顶点数据的类型
typedef struct ArcNode {int adjvex;//邻接点在数组中的位置下标struct ArcNode* nextarc;//指向下一个邻接点的指针
}ArcNode;
typedef struct VNode {VertexType data;//顶点的数据域ArcNode* firstarc;//指向邻接点的指针
}VNode, AdjList[MAX_VERTEX_NUM];//存储各链表头结点的数组
typedef struct {AdjList vertices;//图中顶点及各邻接点数组int vexnum, arcnum;//记录图中顶点数和边或弧数
}ALGraph;//找到顶点对应在邻接表数组中的位置下标
int LocateVex(ALGraph G, VertexType u) {for (int i = 0; i < G.vexnum; i++) {if (G.vertices[i].data == u) {return i;}}return -1;
}//构建AOV网,构建邻接表
//AOV网
//在一个工程的有向图中 顶点表示活动 边代表活动之间的优先关系void CreateG(ALGraph** G)
{*G = (ALGraph*)malloc(sizeof(ALGraph));VertexType v1, v2;printf("输入总顶点数和弧数\n");scanf_s("%d %d", &(*G)->vexnum, &(*G)->arcnum);getchar();printf("输入各个顶点的值\n");for (int i = 0; i < (*G)->vexnum; i++){scanf_s("%d", &((*G)->vertices[i].data));getchar();(*G)->vertices[i].firstarc = NULL;}for (int i = 0; i < (*G)->vexnum; i++){printf("弧的两个端点:\n");scanf_s("%c", &v1);getchar();scanf_s("%c", &v2);getchar();//找到这两个值所对应的顶点的位置//int Locate01 = LocateVex((*G), v1);//int Locate02 = LocateVex((*G), v2);ArcNode* p = (ArcNode*)malloc(sizeof(ArcNode));p->adjvex = LocateVex(*(*G), v2);p->nextarc = NULL;int Locate = LocateVex(*(*G), v1);p->nextarc = (*G)->vertices[Locate].firstarc;(*G)->vertices[Locate].firstarc = p;}
}
图构建好之后,如果我们根据上述的步骤去推写,那么我们写出来的代码有可能是一个暴力搜索的代码,也就是每一次搜索都需要循环一次;
我们在上文的举例中也可以看到,其实一开始我们就已经有两个节点 V1 和 V5 满足没有前驱的条件了,那么我们可不可以在一次循环中就把当前满足条件的节点给找出来,是有这种方法的吧!回想在线性表中我们学到过:栈和队列,用这种数据结构就可以去存储相对应的点,这里我们用栈来实现;
//结构体定义栈结构
typedef struct stack {VertexType data;struct stack* next;
}stack;
//初始化栈结构
void Stack_Init(stack** S) {(*S) = (stack*)malloc(sizeof(stack));(*S)->next = NULL;
}
//判断链表是否为空
bool Stack_Empty(stack S) {if (S.next == NULL) {return true;}return false;
}
//进栈,以头插法将新结点插入到链表中
void Stack_Push(stack* S, VertexType u) {stack* p = (stack*)malloc(sizeof(stack));p->data = u;p->next = NULL;p->next = S->next;S->next = p;
}
//弹栈函数,删除链表首元结点的同时,释放该空间,并将该结点中的数据域通过地址传值给变量 i;
void Stack_Pop(stack *S,VertexType *i){
stack* p = S->next;
*i = p->data;
S->next = S->next->next;
free(p);
}
//统计各顶点的入度
void FindInDegree(ALGraph G, int* indegree) {//初始化数组,默认初始值全部为 0for (int i = 0; i < G.vexnum; i++) {indegree[i] = 0;}//遍历邻接表,根据各链表中结点的数据域存储的各顶点位置下标,在 indegree 数组相应位置+1for (int i = 0; i < G.vexnum; i++) {ArcNode* p = G.vertices[i].firstarc;while (p) {indegree[p->adjvex]++;p = p->nextarc;}}
}
当然,我们还需要有一个数组indegree来记录顶点的入度;
int indegree[200];
构建出来的邻接表G和入度数组indegree如下图所示:
在一开始我就找到了两个入度为0的点 V1 和 V5,所以把 V1 和 V5 入栈;
然后 V5 弹栈,在邻接表中找到下标为4的顶点集,发现 V5 的出度有 下标为1(即V2)和下标为5(即V6)的点,于是在indegree数组中把下标为1和下标为5的元素的值减一;
同样的,V1弹栈,V3 和 V4的入度减一;这时候我们再去找入度为0的点,发现这时 V3 V4 的入度为0,入栈,
弹出V4, V2和V6的入度减一,V2入度为0,入栈;又因为V2没有出度的点,所以V2弹栈;
V3弹栈,V6入度减一;此时 V6 入度为0入栈;V6是最后一个顶点,V6 弹栈;这时indegree中所有下标的值都为0了,并且加入topo序列中的顶点数 == 图中的总顶点数,所以至此topo排序结束;
这便是我们通过代码找出的其中一种topo序列(注意!不同代码逻辑找出的序列的顺序不一样,但只要能完成上文中描述的topo排序的作用就行);
int TopoSort(ALGraph* G, int* topo)
{int indegree[200];FindInDegree((*G), indegree);//建立栈stack* S;Stack_Init(&S);//先检查所有入度为0的顶点for (int i = 0; i < G->vexnum; i++){//把入度为0的顶点入栈if (!indegree[i]){Stack_Push(S, i);}}//cnt用来记录topo序列的个数 如果cnt小于顶点总个数 说明图有环int cnt = 0;//只要栈不为空 就把栈顶元素记录在topo数组中 并出栈while (!Stack_Empty(*S)){int index;//弹栈,并记录栈中保存的顶点所在邻接表数组中的位置Stack_Pop(S, &index);topo[cnt] = index;cnt++;//依次查找跟该顶点相链接的顶点,如果初始入度为 1,当删除前一个顶点后,该顶点入度为 0for (ArcNode* p = G->vertices[index].firstarc; p; p = p->nextarc){//记录顶点的下标VertexType k = p->adjvex;//相对应的数组的下标减一indegree[k]--;if (indegree[k] == 0){Stack_Push(S, k);}}}topo[cnt] = -1;//topo序列结束位的标志
代码运行结果;
总结
topo排序的逻辑比较简单,代码实现也不算复杂,但是topo对想要走研发方向的同学十分重要,并且在后面的课程也会用到,所以还有不清楚的自己画一画、捋一捋,也可以私信问我,我会尽可能解答;我们一起加油!
全部代码如下:
//拓扑排序
//具有n个顶点的有向图 只要满足vi到vj并且要vi在vj之前 对于这种顶点序列就是topo序列
//算法思想 在有向图中选取一个没有前驱的顶点 输出(放进序列 在图中删除这个顶点和以这个顶点为尾的弧(出度)
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>#define MAX_VERTEX_NUM 20//最大顶点个数
#define VertexType int//顶点数据的类型
#define ERR 0
#define OK 1
typedef struct ArcNode {int adjvex;//邻接点在数组中的位置下标struct ArcNode* nextarc;//指向下一个邻接点的指针
}ArcNode;
typedef struct VNode {VertexType data;//顶点的数据域ArcNode* firstarc;//指向邻接点的指针
}VNode, AdjList[MAX_VERTEX_NUM];//存储各链表头结点的数组
typedef struct {AdjList vertices;//图中顶点及各邻接点数组int vexnum, arcnum;//记录图中顶点数和边或弧数
}ALGraph;
//找到顶点对应在邻接表数组中的位置下标
int LocateVex(ALGraph G, VertexType u) {for (int i = 0; i < G.vexnum; i++) {if (G.vertices[i].data == u) {return i;}}return -1;
}//构建AOV网,构建邻接表
//AOV网
//在一个工程的有向图中 顶点表示活动 边代表活动之间的优先关系void CreateG(ALGraph** G)
{*G = (ALGraph*)malloc(sizeof(ALGraph));VertexType v1, v2;printf("输入总顶点数和弧数\n");scanf("%d %d", &(*G)->vexnum, &(*G)->arcnum);getchar();printf("输入各个顶点的值\n");for (int i = 0; i < (*G)->vexnum; i++){scanf("%d", &((*G)->vertices[i].data));getchar();(*G)->vertices[i].firstarc = NULL;}for (int i = 0; i < (*G)->arcnum; i++){printf("弧的两个端点:\n");scanf("%d", &v1);getchar();scanf("%d", &v2);getchar();//找到这两个值所对应的顶点的位置//int Locate01 = LocateVex((*G), v1);//int Locate02 = LocateVex((*G), v2);ArcNode* p = (ArcNode*)malloc(sizeof(ArcNode));p->adjvex = LocateVex(*(*G), v2);p->nextarc = NULL;int Locate = LocateVex(*(*G), v1);p->nextarc = (*G)->vertices[Locate].firstarc;(*G)->vertices[Locate].firstarc = p;}
}//结构体定义栈结构
typedef struct stack {VertexType data;struct stack* next;
}stack;
//初始化栈结构
void Stack_Init(stack** S) {(*S) = (stack*)malloc(sizeof(stack));(*S)->next = NULL;
}
//判断链表是否为空
bool Stack_Empty(stack S) {if (S.next == NULL) {return true;}return false;
}
//进栈,以头插法将新结点插入到链表中
void Stack_Push(stack* S, VertexType u) {stack* p = (stack*)malloc(sizeof(stack));p->data = u;p->next = NULL;p->next = S->next;S->next = p;
}
//弹栈函数,删除链表首元结点的同时,释放该空间,并将该结点中的数据域通过地址传值给变量 i;
void Stack_Pop(stack *S,VertexType *i){
stack* p = S->next;
*i = p->data;
S->next = S->next->next;
free(p);
}
//统计各顶点的入度
void FindInDegree(ALGraph G, int* indegree) {//初始化数组,默认初始值全部为 0for (int i = 0; i < G.vexnum; i++) {indegree[i] = 0;}//遍历邻接表,根据各链表中结点的数据域存储的各顶点位置下标,在 indegree 数组相应位置+1for (int i = 0; i < G.vexnum; i++) {ArcNode* p = G.vertices[i].firstarc;while (p) {indegree[p->adjvex]++;p = p->nextarc;}}
}int TopoSort(ALGraph* G, int* topo)
{int indegree[200];FindInDegree((*G), indegree);//建立栈stack* S;Stack_Init(&S);//先检查所有入度为0的顶点for (int i = 0; i < G->vexnum; i++){//把入度为0的顶点入栈if (!indegree[i]){Stack_Push(S, i);}}//cnt用来记录topo序列的个数 如果cnt小于顶点总个数 说明图有环int cnt = 0;//只要栈不为空 就把栈顶元素记录在topo数组中 并出栈while (!Stack_Empty(*S)){int index;//弹栈,并记录栈中保存的顶点所在邻接表数组中的位置Stack_Pop(S, &index);topo[cnt] = index;cnt++;//依次查找跟该顶点相链接的顶点,如果初始入度为 1,当删除前一个顶点后,该顶点入度为 0for (ArcNode* p = G->vertices[index].firstarc; p; p = p->nextarc){//记录顶点的下标VertexType k = p->adjvex;//相对应的数组的下标减一indegree[k]--;if (indegree[k] == 0){Stack_Push(S, k);}}}topo[cnt] = -1;//topo序列结束位的标志if (cnt < G->vexnum){printf("该图存在环路\n");return ERR;}else{printf("该图不存在环路");for (int i = 0; topo[i] != -1; i++){printf("%d", G->vertices[topo[i]].data);}return OK;}
}int main()
{ALGraph* G;CreateG(&G);int topo[MAX_VERTEX_NUM];TopoSort(G, topo);system("pause");return 0;
}
图解Topo拓扑排序相关推荐
- 图解Topo拓扑排序 例题洛谷P4017 最大食物链计数
适用条件:无环图 输出结果:使得每个节点,以它为终点的起点节点排都在其前面 作用:递推保证前面的节点都已经被使用过 实现方法:从没有入边的节点开始,输出并从其他节点中删去自己,重复此步骤直到所有节点都 ...
- aov建立Java模拟,数据结构之---C语言实现拓扑排序AOV图
//有向图的拓扑排序 //杨鑫 #include #include #include #define MAX_NAME 3 #define MAX_VERTEX_NUM 20 typedef int ...
- 拓扑排序介绍及其应用
拓扑排序简介 拓扑排序是一种图论排序方式,要了解拓扑排序,首先要了解两个图论概念:入度和出度.在有向无环图( D A G DAG DAG)中,一个点是多少弧的弧头称为入度,是多少弧的弧尾是出度. 而作 ...
- 数据结构与算法A实验六图论---7-7 最短工期 (拓扑排序)
一个项目由若干个任务组成,任务之间有先后依赖顺序.项目经理需要设置一系列里程碑,在每个里程碑节点处检查任务的完成情况,并启动后续的任务.现给定一个项目中各个任务之间的关系,请你计算出这个项目的最早完工 ...
- 洛谷P1983 车站分级 拓扑排序
题目链接:https://www.luogu.com.cn/problem/P1983 此题是用拓扑排序求层数,将等级高的点连向等级的点,连成的图用拓扑排序看一下多少层,即答案. 代码如下 #incl ...
- 拓扑排序之java实现_有向图和拓扑排序Java实现
package practice; import java.util.ArrayDeque; import java.util.Iterator; import java.util.Stack; pu ...
- 超详细讲解实现拓扑排序、关键路径
今天,小编带着大家来学习图中非常重要的一环,拓扑排序和关键路径! 目录 一. 绪论--实际应用 二. 拓扑排序 (一).含义 (二).实现原理 (三).代码实现 三. 关键路径 (一).含义 (二). ...
- 图解:有向环、拓扑排序与Kosaraju算法
25张图详解有向环.拓扑排序与Kosaraju算法.有向环如何检测?拓扑排序的原理?Kosaraju算法又是如何得到的?本文告诉你答案 首先来看一下今天的内容大纲,内容非常多,主要是对算法思路与来源的 ...
- 拓扑排序Kahn算法
拓扑排序 介绍 思路 操作过程 完美图解 代码模板 介绍 拓扑排序,整体是给出n个事件先后关系,来确定n个事件最终的先后关系 思路 很好理解 操作过程 我们可以理解成一个有向图如果x事件在y事件的前面 ...
最新文章
- 一次愉快的 bug 修复经历
- SQLserver 2000 重新安装挂起的解决方法
- Python PIL反色混合
- 移动IM开发指南1:如何进行技术选型
- 【方案分享】华为MateBook X Pro上市数字传播方案.pptx(附下载链接)
- DOM节点的插入、替换、克隆及删除
- Linux shell篇---之一--shell基础
- [渝粤教育] 山东财经大学 国际经济学 参考 资料
- python选择哪个版本-Python学习,要选哪个版本?
- java计算器流程图_我的第一个JAVA程序之计算器
- 学渣上手 LaTeX 完成毕业论文
- DCDC BUCK芯片外围电路器件计算-包括Cin、Cout、L、f、cFF、等输出电容、电感计算、补偿电路计算
- 一文搞懂 deconvolution、transposed convolution、sub-pixel or fractional convolution
- 线性代数Python计算:无关向量组的正交化
- 图像处理——分水岭算法
- Linux学习和阿里云服务器的配置(感谢秦老师)
- 墙裂推荐4款js网页烟花特效
- php 英文单词 数,【PHP】统计中英文单词数(GB2312/UTF-8编码)
- 如何通过cmd查看python版本
- 苹果电脑的3d性能测试软件,苹果iMac 2014电脑3D及游戏性能测试
热门文章
- vue-seamless-scroll大屏抽奖滚动动画实例
- 地图可视化绘制 | R-tanaka/metR包 绘制3D阴影效果地图
- composer介绍
- Cannot use import statement outside /npm WARN saveError ENOENT: no such file or directory
- android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context...
- torch.nn.CosineSimilarity() 三维张量
- Error: Java exception was raised during method invocation
- 【金融量化分析】#HW2 (Effective return;Duration and convexity;bootstrap method; minimum variance portfolios)
- Stream Collectors - reducing
- python程序实例下载_Python经典实例