拓扑排序(数据结构之图的应用)

我们先搞清楚一个概念:

什么是出度与入度?
在有向图中,箭头是具有方向的,从一个顶点指向另一个顶点,这样一来,每个顶点被指向的箭头个数,就是它的入度。从这个顶点指出去的箭头个数,就是它的出度

 1、选择任意一个入度为0的节点(即该节点没有被任意一个箭头指向),并将该节点输出,然后在图中删除该节点和从他出发的有向边(有向边被指向的所有节点入度减1

 2、删除完后,下一个入度为0的节点是5,将5输出,并且删除节点5,并删除从节点5出发指向2和6的有向边。(有向边被指向的节点2和节点6 入度减1

3、此处,2和6都是入度为0的节点,可以选择任意一个操作,这里我们选择2操作,重复步骤

4、从入度为0的节点3和节点6中任意选择一个重复上述步骤,这里我们选择节点3,并将3输出,删除节点3和从3出发的有向边(有向边被指向的节点3 入度减1

 

 5、发现只剩下一个(入度为0)的节点6,将6输出,删除节点6和从6出发的有向边(有向边被指向的节点4 入度减1),再选择4,删除,输出.

最终得到的结果是    1 5 2 3 6 4

我们刚刚如果在面临2个同时入度为0的节点时,选择的不一样,会造成拓扑排序的序列不一致,因此,拓扑排序序列并不唯一。(存在多种可能

我们来看下面一道例题

 根据邻接表画图的结构

 以上这个图被称为邻接表,第一列的数组的所有数据(A,B,C,D,E)记载的是图中每一个节点的连接情况,我们用0表示A,1表示B,即(A=0 B=1 C=2 D=3 E=4),正式的邻接表不存在字母的,将数字代替字母。

每个节点由两部分组成,前面存储数据,后面存储指针(箭头的指向),若它没有下个指向的目标了,则存储 ^ 符号,表示 下个指向 为空    如果该节点同时指向多个不同的节点,则按照从节点小到大顺序,依次首尾相连,例如 上图中的A与节点1(B)和节点2(C)

 拓扑排序要注意的一点是,如果图的结果构成一个环,则该图没有拓扑排序

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,让我们先来了解一下什么是拓扑排序和关键路径。 拓扑排序是对有向无环图(DAG)进行排序的一种方法。它可以将一个DAG的顶点排成一条线性序列,使得对于任何一条有向边 (u, v),顶点 u 在序列都排在顶点 v 的前面。 关键路径是指在一个有向无环图,从起点到终点的所有路径,耗时最长的那条路径。在实际应用,关键路径可以用来确定项目的最短工期,以及哪些任务是关键任务,不能延误。 下面是用C语言实现拓扑排序和关键路径的代码,注释有详细的解释。 ```c #include <stdio.h> #include <stdlib.h> #define MAX_VERTEX_NUM 100 // 图最大顶点数 #define MAX_EDGE_NUM 100 // 图最大边数 // 边的结构体,包含起点和终点 typedef struct { int from; // 起点 int to; // 终点 } Edge; // 顶点的结构体,包含入度和出度 typedef struct { int in; // 入度 int out; // 出度 } Vertex; // 图的结构体,包含顶点数组、边数组、顶点数和边数 typedef struct { Vertex vertices[MAX_VERTEX_NUM]; // 顶点数组 Edge edges[MAX_EDGE_NUM]; // 边数组 int vertex_num; // 顶点数 int edge_num; // 边数 } Graph; // 初始化图 void init_graph(Graph *g) { int i; g->vertex_num = 0; g->edge_num = 0; for (i = 0; i < MAX_VERTEX_NUM; i++) { g->vertices[i].in = 0; g->vertices[i].out = 0; } } // 添加边 void add_edge(Graph *g, int from, int to) { g->edges[g->edge_num].from = from; g->edges[g->edge_num].to = to; g->edge_num++; g->vertices[from].out++; // 起点出度加1 g->vertices[to].in++; // 终点入度加1 } // 拓扑排序 void topological_sort(Graph *g) { int i, j, k, n; int queue[MAX_VERTEX_NUM]; // 存储入度为0的顶点 int head = 0, tail = 0; // 队列头和尾 int count = 0; // 已排序的顶点数 Edge *e; // 将入度为0的顶点加入队列 for (i = 0; i < g->vertex_num; i++) { if (g->vertices[i].in == 0) { queue[tail++] = i; } } // 循环直到队列为空 while (head != tail) { n = tail - head; // 当前队列顶点数 for (i = 0; i < n; i++) { j = queue[head++]; // 取出队列头 printf("%d ", j); // 输出已排序的顶点 count++; // 已排序的顶点数加1 for (k = 0; k < g->edge_num; k++) { e = &g->edges[k]; if (e->from == j) { // 找到以j为起点的边 g->vertices[e->to].in--; // 对应终点的入度减1 if (g->vertices[e->to].in == 0) { // 如果终点入度为0,则加入队列 queue[tail++] = e->to; } } } } } if (count < g->vertex_num) { // 如果已排序的顶点数小于总顶点数,则存在环路 printf("The graph has a cycle\n"); } } // 计算关键路径 void critical_path(Graph *g) { int i, j, k, m = 0, n = 0; int earliest[MAX_VERTEX_NUM] = {0}; // 存储最早开始时间 int latest[MAX_VERTEX_NUM] = {0}; // 存储最晚开始时间 Edge *e; // 计算最早开始时间 for (i = 0; i < g->vertex_num; i++) { for (j = 0; j < g->edge_num; j++) { e = &g->edges[j]; if (e->from == i) { // 找到以i为起点的边 if (earliest[e->to] < earliest[i] + 1) { // 更新终点的最早开始时间 earliest[e->to] = earliest[i] + 1; } } } } // 计算最晚开始时间 for (i = g->vertex_num - 1; i >= 0; i--) { latest[i] = earliest[g->vertex_num - 1]; // 先初始化为总工期 for (j = 0; j < g->edge_num; j++) { e = &g->edges[j]; if (e->from == i) { // 找到以i为起点的边 if (latest[i] > latest[e->to] - 1) { // 更新起点的最晚开始时间 latest[i] = latest[e->to] - 1; } } } } printf("The critical path is: "); for (i = 0; i < g->edge_num; i++) { e = &g->edges[i]; if (earliest[e->to] - earliest[e->from] == latest[e->to] - latest[e->from]) { // 如果边的最早开始时间和最晚开始时间相等,则为关键边 printf("(%d,%d) ", e->from, e->to); if (earliest[e->to] > m) { // 找到最大的最早开始时间 m = earliest[e->to]; } if (latest[e->from] < n) { // 找到最小的最晚开始时间 n = latest[e->from]; } } } printf("\nThe minimum time to finish the project is %d\n", m - n); } int main() { Graph g; init_graph(&g); g.vertex_num = 7; // 设置顶点数 add_edge(&g, 0, 1); add_edge(&g, 0, 2); add_edge(&g, 1, 3); add_edge(&g, 1, 4); add_edge(&g, 2, 3); add_edge(&g, 2, 5); add_edge(&g, 3, 6); add_edge(&g, 4, 6); add_edge(&g, 5, 6); topological_sort(&g); critical_path(&g); return 0; } ``` 上述代码,我们定义了一个Graph的结构体来表示图,包含顶点数组、边数组、顶点数和边数。同时定义了一个Vertex的结构体来表示顶点,包含入度和出度。定义了一个Edge的结构体来表示边,包含起点和终点。然后分别实现了初始化图、添加边、拓扑排序和计算关键路径的函数。 在main函数,我们先初始化图,然后添加边,设置顶点数为7。然后调用拓扑排序和计算关键路径的函数来输出结果。 这段代码可能对于数据结构初学者来说有些难度,但是只要认真看注释,理解了拓扑排序和关键路径的原理,就能够理解代码的实现过程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值