200字范文,内容丰富有趣,生活中的好帮手!
200字范文 > 图解Topo拓扑排序

图解Topo拓扑排序

时间:2023-06-18 22:38:56

相关推荐

图解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 1typedef 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;}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。