数据结构(C语言实现)-图(4)(有向无环图:拓扑排序与关键路径)

有向无环图(DAG图)就是没有环路的有向图,即以任意一个顶点为起点出发,走任意一条路径也不能回到起点。
有向无环图常用来描述一项工程的进行过程,这个工程分为一系列子工程,而一些子工程的开始必须以某些子工程的结束为条件。
对应于一个工程或系统,人们常常关心两个问题:(1)工程能否顺利进行,这是拓扑排序问题。(2)工程完成所需要的最短时间,这是关键路径问题。

拓扑排序

由某个集合上的一个偏序,得到该集合上的一个全序

偏序:R是集合上的二元关系,R满足(1)自反性(2)反对称性(3)传递性
全序:对于集合上的二元关系,满足(1)完全性(2)反对称性(3)传递性(完全性包含了自反性)
直观理解:偏序是集合中只有部分元素间可比较(先后、大小等),全序是集合中任意元素间都可比较。

顶点表示活动,弧表示活动间先后关系的有向图称为AOV网(顶点表示活动的网)。
拓扑排序步骤:重复以下步骤:(1)在有向图中选取一个没有前驱的顶点,输出之。(2)图中删除该点和所有与之相连的弧。直至图中不再有顶点(活动)存在。

我们用邻接表作为图的存储结构,稍有的一点改变是在顶点节点增加一个数据域,用来存储顶点入度。同时要使用一个栈来存储入度为零的顶点。邻接表和栈的实现用前面的代码。以下是拓扑排序代码实现:

int TopologicalSort(ALgraph *G)
{
	SqStack s;
	InitStack(s);
	int indegree[G.vexnum-1]=0;      //设置辅助数组,实时记录每个顶点的入度
	FindInDegree(G,indegree);        //辅助数组初始化,统计初始图中每个顶点的入度
	for(int i = 0;i<G.vexnum-1;i++)
		if(!indegree[i]) Push(s,i);  //入度为0的顶点的序号入栈
	
	int count = 0;                   //设一个计数变量,用来判断该图是否有环
	while(s.top != s.base)           //栈不为空
	{
		int i;
		Pop(s,i);
		printf("按拓扑排序顺序输出顶点,接下来的顶点是:%c",G.vertices[i].data);
		count++;                     //每输出一个顶点,计数加一
		for(ArcNode p = G.vertices[i].firstarc; p; p=p->nextarc)//与第i个顶点相连的所有弧
		{
			int j = p->adjvex;       //弧指向的顶点
			if(!(--indegree[j]))     //入度减一
				Push(s,j);           //若入度为0,入栈
		}
	}

	if(count<G.vexnum)
		return ERROR;  //说明该有向图有环       
	else
		return OK;
}

void FindInDegree(ALGraph G,int* indegree)
{
	for(int i = 0;i<G.vexnum-1;i++)
	{
		for(ArcNode p = G.vertices[i].firstarc; p; p=p->nextarc)//与第i个顶点相连的所有弧
		{
			int j = p->adjvex;       //弧指向的顶点
			indegree[j]++;
		}
	}
}

关键路径

弧表示活动,权值表示活动持续时间,顶点表示时间节点的有向图称为AOE网(边表示活动的网
在AOE网中,有些活动可以并行的进行,所以完成工程的最短时间是由开始点到结束点的最长路径长度(路径上各活动持续时间之和),这条路径就叫关键路径。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,让我们先来了解一下什么是拓扑排序关键路径拓扑排序是对有向无环图(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。然后调用拓扑排序和计算关键路径的函数来输出结果。 这段代码可能对于数据结构初学者来说有些难度,但是只要认真看注释,理解了拓扑排序关键路径的原理,就能够理解代码的实现过程。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值