数据结构刚讲完图的应用中的Prim算法、Kruskal算法、拓扑排序与关键路径几节,总结一下拓扑排序与关键路径中的一些重要概念,以及求关键路径中的一些主要思路。
拓扑排序
AOV网
定义:
用顶点表示活动,弧表示活动间优先关系的有向无环图。
研究重点:
顶点表示活动的先后顺序。(如果存在环的话则会出现活动需要在自身完成之前被完成这种矛盾的情况,所以拓扑排序只适用于有向无环图)
拓扑排序就是构造拓扑序列的操作过程,拓扑序列是具有以下性质的线性序列:
网中的所有顶点都在该序列中
若顶点Vi到Vj存在一条路径,则Vi一定排在Vj之前。(反之不成立)
逆拓扑中:顶点Vi到Vj存在一条路径,则Vi一定排在Vj之后。(反之不成立)
求逆拓扑序列:由拓扑序列逆置即可。
利用AOV网求拓扑序列
- 从有向图中选一个没有前驱的顶点输出。
- 从有向图中删除该顶点以及从他出发的所有弧。
重复以上步骤直至找不到无前驱的顶点,此时若图空,则说明图中无环;若图不空,则说明图中存在回路。
分析:
“没有前驱”=====顶点入度为0;
“删除为弧尾的弧”=====弧头的顶点入度减1
具体实现中:
采用邻接矩阵为存储结构
1.按下标查找全列为0对应的Vi,存入拓扑序列;
2.找到Vi对应的一行全部化为0(即删去了该顶点及所有以它为尾的弧);
3.重复上述1.2.操作
采用邻接表为存储结构
查找顶点、删除顶点与删除弧均与顶点的入度相关,故应设置一个数组indegree【】用以记录每个顶点的入度值。
1.创建indegree【】,初值均为零,通过扫描邻接表的各条链,遇到顶点Vi,则将对应的indegree【i】值加1,获得存储每个顶点入度值的数组。
为了避免重复查找度为0的顶点,可设置一个队列或者栈暂存入度为0的顶点,是的每次查找入队的为0的顶点时只须做出队或出栈操作,而不必每次都查找整个indegre【】数组。而当某个顶点的入度一旦减为0就要做入队或入栈操作。
2.将入队为0的顶点全部放入队列或者栈中。
3.执行出队或者出栈操作,得到的Vi加入拓扑序列中,同时遍历邻接表中Vi对应的那条链表,将与其有关联的结点的入度均减一。
4.重复2.3.
注:
拓扑序列不是唯一的,原因有:
1.可能同时存在多个没有前驱的顶点。
2.用来存储拓扑序列的存储结构不一定。
但,一旦存储结构与算法确定,拓扑序列就是唯一确定的。
时间复杂度
对有n个顶点和e条边的图来说,若用邻接表表示:建立存储拓扑序列的队和栈需要把所有顶点检查一次——O(n);更新入度的过程中把每条边都检查了一次——O(e),所以总时间为O(n+e)。
关键路径
AOE网
定义:
用顶点表示事件,用有向边表示活动,用边的权值表示活动持续的时间的带权有向无环网。
研究重点:
估算工程的完成时间。
性质:
1. 只有顶点表示的事件发生了之后,从该顶点出发的弧表示的活动才能开始。
2. 只有指向一个顶点的所有弧表示的活动都已完成之后,该顶点所表示的事件才能发生。
对于只有一个开始点(源点)和一个完成点(汇点)的工程可以用AOE网来表示。
分析:事件表示一种状态,事件发生即工程进行到了某一状态,事件发生是一个时间点。
利用AOE网求关键路径
定义与计算方法:
- 关键路径:从源点到汇点的路径中长度最长的一条路径。
Vi的最早发生时间
====从源点V1到Vi的最长路径长度。
==== 以Vi为尾的弧所表示的活动ai的最早开始时间,用e(ai)表示。
用ve(i)表示。Vi的最迟发生时间
====从顶点i到汇点的最短路径长度;
====在保证汇点事件按其最早发生时间发生这一前提下,以Vi做弧尾的所有弧的弧头的最迟发生时间减去每条弧表示的活动的持续时间所得差的最小值。ai的最晚开始时间
====在不推迟整个工程完成的前提下,活动ai最晚必须开始的时间。
====ai的弧头的最晚发生时间减去ai的持续时间。
用l(ai)表示。关键活动
====当l(ai)=e(ai)时,活动ai为关键活动。
====关键路径上的所有活动都是关键活动。
分析关键路径的目的在于求出关键活动,争取提高关键活动的工效,从而缩短工期。
求解关键路径的步骤:
- 对顶点进行拓扑排序;
- 求出每个事件的最早、最晚发生时间;
- 计算每个活动的最早、最晚开始时间;
- 找出e(ai)=l(ai)的关键活动。
对计算ve(j)与vl(i)公式的理解:
事件j在所有前驱活动都完成后发生,所以其最早发生时间为ve(j)=max{ve()+a(,j)},即取决于他的最慢前驱; 而另一方面,事件i发生后所有后继活动都可以开始了,所以其最晚发生时间vl(i)=min{vl()-a(i,)},即在保证汇点按其最早发生时间发生的前提下,不耽误最慢的后继活动