图(三):拓扑排序、关键路径
发布日期:2021-05-07 06:30:34 浏览次数:16 分类:原创文章

本文共 5154 字,大约阅读时间需要 17 分钟。

作为数据结构的课程笔记,以便查阅。如有出错的地方,还请多多指正!

注:C++忘得太厉害了。。算法先用C实现,等之后复习了再改成C++

目录

  • 有向无环图 / DAG图(directed acycline graph)

拓扑排序 Topological Sort

问题提出:学生选修课程问题
学生应按怎样的顺序学习这些课程,才能无矛盾、顺利地完成学业?

在这里插入图片描述

  • AOV网——用顶点表示活动,用弧表示活动间优先关系的有向图称为顶点表示活动的网(Activity On Vertex network)
    AOV网中不允许有回路,这意味着某项活动以自己为先决条件
  • 拓扑排序——把AOV网络中各顶点按照它们相互之间的优先关系排列成一个线性序列

拓扑排序的方法

  • 在有向图中选一个没有前驱的顶点且输出之
  • 从图中删除该顶点和所有以它为尾的弧
  • 重复上述两步,直至全部顶点均已输出;或者当图中不存在无前驱的顶点为止
    在这里插入图片描述

检测AOV网中是否存在环方法:对有向图构造其顶点的拓扑有序序列,若网中所有顶点都在它的拓扑有序序列中,则该AOV网必定不存在环
在这里插入图片描述

算法实现

拓扑排序中,最常用的操作就是求邻接点和入度。因此以邻接表作为存储结构

  • 额外设置一个数组来存储各个结点的入度,或者加入入度域
    在这里插入图片描述
  • 邻接表中所有入度为0的顶点进栈
  • 栈非空时,栈顶元素 V V V出栈并输出到拓扑序列中;将 V V V的所有邻接顶点入度减1,若为0则进栈
    重复上述操作直至栈空为止
  • 若栈空时输出的顶点个数不是 n n n,则有向图有环;否则,拓扑排序完毕
    在这里插入图片描述
    输出序列:6 1 3 2 4 5
static void Calc_indegree(pAdjacentList_t pgraph, int indegree[]){   	for (int i = 0; i < pgraph->vexNum; ++i)	{   		indegree[i] = 0;	}	for (int i = 0; i < pgraph->vexNum; ++i)	{   		pArcNode_t arc;		for (arc = pgraph->vex[i].firstEdge; arc; arc = arc->nextEdge)		{   			indegree[arc->head]++;		}	}}//拓扑排序int Topological_sort(pAdjacentList_t pgraph, void (*visit)(AdjacentListDataType_t data)){   	int* indegree = (int*)malloc(pgraph->vexNum * sizeof(int));	pStackNode_t ptop;	int cnt = 0;//输出的顶点个数	Calc_indegree(pgraph, indegree);	Stack_init(&ptop);	for (int i = 0; i < pgraph->vexNum; ++i)	{   		if (0 == indegree[i])		{   			Push(&ptop, i);		}	}		while (!Stack_is_empty(ptop))	{   		int i;		pArcNode_t arc;		Pop(&ptop, &i);		visit(pgraph->vex[i].data);		++cnt;		for (arc = pgraph->vex[i].firstEdge; arc; arc = arc->nextEdge)		{   			if (0 == --indegree[arc->head])			{   				Push(&ptop, arc->head);			}		}	}	free(indegree);	return (cnt == pgraph->vexNum);//是否有环}

T ( n ) = O ( n + e ) T(n)=O(n+e) T(n)=O(n+e)

关键路径 Critical Path

问题提出

设一个工程有11项活动,9个事件
问题:(1) 完成整项工程至少需要多少时间? (2) 哪些活动是影响工程进度的关键?

把工程计划表示为有向图。每个事件表示在它之前的活动已完成,在它之后的活动可以开始
在这里插入图片描述

在这里插入图片描述
只有在不改变关键路径的前提下,提高所有关键路径上的公共活动的效率才能缩短整个工期

基本概念

  • 完成整个工程的最短时间为:从有向图的源点到汇点的最长路径
  • AOE网(Activity On Edge)。AOE网中顶点表示事件,弧表示活动,权表示活动持续时间
  • 关键路径——路径长度最长的路径
  • V e ( j ) V_e(j) Ve(j)——表示事件 V j V_j Vj的最早发生时间
  • V l ( j ) V_l(j) Vl(j)——表示事件 V j Vj Vj的最迟发生时间(不影响工期)
  • e ( i ) e(i) e(i)——表示活动 a i a_i ai的最早开始时间
  • l ( i ) l(i) l(i)——表示活动 a i a_i ai的最迟开始时间(不影响工期)
  • l ( i ) − e ( i ) l(i)-e(i) l(i)e(i)——表示完成活动 a i a_i ai的时间余量
  • 关键活动—— l ( i ) = e ( i ) l(i)=e(i) l(i)=e(i)的活动,由关键活动组成关键路径

问题分析

  • 如何找 e ( i ) = l ( i ) e(i)=l(i) e(i)=l(i)的关键活动?
    设活动 a i a_i ai用弧 < j , k > <j,k> <j,k>表示,其持续时间记为: d u t ( < j , k > ) dut(<j,k>) dut(<j,k>)
    则有: e ( i ) = V e ( j ) e(i)=V_e(j) e(i)=Ve(j) l ( i ) = V l ( k ) − d u t ( < j , k > ) l(i)=V_l(k)-dut(<j,k>) l(i)=Vl(k)dut(<j,k>)

  • 如何求 V e ( j ) V_e(j) Ve(j) V l ( j ) V_l(j) Vl(j)
    (1) 从 V e ( 1 ) = 0 V_e(1)=0 Ve(1)=0开始从前向后递推(拓扑有序)
    在这里插入图片描述
    (2)从 V l ( n ) = V e ( n ) V_l(n)=V_e(n) Vl(n)=Ve(n)开始从后向前递推(逆拓扑有序)
    在这里插入图片描述

  • 求关键路径步骤
    V e ( i ) V_e(i) Ve(i)
    V l ( j ) V_l(j) Vl(j)
    e ( i ) = V e ( j ) e(i) =V_e(j) e(i)=Ve(j)
    l ( i ) = V l ( k ) − d u t ( < j , k > ) l(i) =V_l(k)-dut(<j,k>) l(i)=Vl(k)dut(<j,k>)
    计算 l ( i ) − e ( i ) l(i)-e(i) l(i)e(i)

算法实现

  • 使用邻接表,增设入度域(或者一个记录各个结点入度的数组)
  • 计算每个顶点的入度
  • 对各个顶点进行拓扑排序,排序过程中求 V e ( i ) V_e(i) Ve(i)。将得到的拓扑序列入栈,以便之后得到逆拓扑序列
  • 按逆拓扑序列求 V l ( i ) V_l(i) Vl(i)
  • 计算每条弧(活动)的 e [ i ] e[i] e[i] l [ i ] l[i] l[i],找出 e [ i ] = l [ i ] e[i]=l[i] e[i]=l[i]的关键活动
//求图上的关键路径Status_e Critical_path(pAdjacentList_t pgraph){   	int* indegree = (int*)malloc(pgraph->vexNum * sizeof(int));	AdjacentListWeightType_t* ve = (AdjacentListWeightType_t*)malloc(pgraph->vexNum * sizeof(AdjacentListWeightType_t));	AdjacentListWeightType_t* vl = (AdjacentListWeightType_t*)malloc(pgraph->vexNum * sizeof(AdjacentListWeightType_t));	AdjacentListWeightType_t ee;	AdjacentListWeightType_t el;	pStackNode_t ptop;	int cnt = 0;//输出的顶点个数	pStackNode_t ptop_topological;//记录拓扑排序的次序 方便之后按逆序操作	int finish_time = 0;		Calc_indegree(pgraph, indegree);	Stack_init(&ptop);	Stack_init(&ptop_topological);	//入度为0的点入栈	for (int i = 0; i < pgraph->vexNum; ++i)	{   		if (0 == indegree[i])		{   			Push(&ptop, i);		}		ve[i] = 0;		vl[i] = INFINITY;	}	//栈不为空时,拓扑排序	while (!Stack_is_empty(ptop))	{   		int i;		pArcNode_t arc;		Pop(&ptop, &i);		Push(&ptop_topological, i);		++cnt;		for (arc = pgraph->vex[i].firstEdge; arc; arc = arc->nextEdge)		{   			ve[arc->head] = MAX(ve[i] + arc->weight, ve[arc->head]);			if (0 == --indegree[arc->head])			{   				Push(&ptop, arc->head);			}			finish_time = MAX(finish_time, ve[arc->head]);		}	}	if (cnt < pgraph->vexNum)	{   		printf("There is loop in the graph!\r\n");		return err;	}	else {   		printf("Project time:%d\n", finish_time);	}	while (!Stack_is_empty(ptop_topological))	{   		int i;		pArcNode_t arc;		Pop(&ptop_topological, &i);		arc = pgraph->vex[i].firstEdge;		if (NULL == arc)		{   			vl[i] = finish_time;		}		for (; arc; arc = arc->nextEdge)		{   			vl[i] = MIN(vl[arc->head] - arc->weight, vl[i]);		}	}	printf("Critical path:\r\n");	for (int i = 0; i < pgraph->vexNum; ++i)	{   		pArcNode_t arc;		for (arc = pgraph->vex[i].firstEdge; arc; arc = arc->nextEdge)		{   			ee = ve[i];			el = vl[arc->head] - arc->weight;			if (ee == el)			{   				printf("%d -> %d ( weight: %d ; ee/el : %d )\r\n", i, arc->head, arc->weight, ee);			}		}	}	free(indegree);	free(ve);	free(vl);	return ok;}

T ( n ) = O ( n + e ) T(n)=O(n+e) T(n)=O(n+e)

上一篇:CSS的使用方式和优势
下一篇:java基础第一章

发表评论

最新留言

网站不错 人气很旺了 加油
[***.192.178.218]2025年04月09日 22时44分18秒