假定一个工程项目由一组子任务构成,子任务之间有的可以并行执行,有的必须在完成了其它一些子任务后才能执行。“任务调度”包括一组子任务、以及每个子任务可以执行所依赖的子任务集。
比如完成一个专业的所有课程学习和毕业设计可以看成一个本科生要完成的一项工程,各门课程可以看成是子任务。有些课程可以同时开设,比如英语和C程序设计,它们没有必须先修哪门的约束;有些课程则不可以同时开设,因为它们有先后的依赖关系,比如C程序设计和数据结构两门课,必须先学习前者。
但是需要注意的是,对一组子任务,并不是任意的任务调度都是一个可行的方案。比如方案中存在“子任务A依赖于子任务B,子任务B依赖于子任务C,子任务C又依赖于子任务A”,那么这三个任务哪个都不能先执行,这就是一个不可行的方案。
任务调度问题中,如果还给出了完成每个子任务需要的时间,则我们可以算出完成整个工程需要的最短时间。在这些子任务中,有些任务即使推迟几天完成,也不会影响全局的工期;但是有些任务必须准时完成,否则整个项目的工期就要因此延误,这种任务就叫“关键活动”。
请编写程序判定一个给定的工程项目的任务调度是否可行;如果该调度方案可行,则计算完成整个工程项目需要的最短时间,并输出所有的关键活动。
输入格式:
输入第1行给出两个正整数N(≤100)和M,其中N是任务交接点(即衔接相互依赖的两个子任务的节点,例如:若任务2要在任务1完成后才开始,则两任务之间必有一个交接点)的数量。交接点按1N编号,M是子任务的数量,依次编号为1M。随后M行,每行给出了3个正整数,分别是该任务开始和完成涉及的交接点编号以及该任务所需的时间,整数间用空格分隔。
输出格式:
如果任务调度不可行,则输出0;否则第1行输出完成整个工程项目需要的时间,第2行开始输出所有关键活动,每个关键活动占一行,按格式“V->W”输出,其中V和W为该任务开始和完成涉及的交接点编号。关键活动输出的顺序规则是:任务开始的交接点编号小者优先,起点编号相同时,与输入时任务的顺序相反。
输入样例:
7 8
1 2 4
1 3 3
2 4 5
3 4 3
4 5 1
4 6 6
5 7 5
6 7 2
输出样例:
17
1->2
2->4
4->6
6->7
AOE拓扑排序的综合问题
可求总工期时常和每个任务的机动时间
每一条边代表一个项目;
每一个结点代表一个任务交接点;
1.求各边的最短工期;
2.倒着,求各点最早开始的时间;
3.两者相减得到各任务的机动时间,按顺序输出机动时间为0的任务即为关键点路径;
A【】【】存放每个任务所需的时间;
D【】【】存放每个任务的机动时间;
Earliest【】存放每个结点的最早开始时间;
Latest【】存放每个结点的最晚开始时间;
当前一个结点的最早开始时间+该任务所需时间=后一个结点的最晚开始时间,说明该任务的机动时间为0,即为关键任务;
1.main函数
#include
#include
using namespace std;
#define MAX 105
#define INFINITY 65535
int N,M,A[MAX][MAX],ECT,EarliestTime[MAX]={0},LatestTime[MAX],D[MAX][MAX],idx;
将任务所需时间保存在A【】【】中,初始化各任务机动时间为inf;
TopSort_Earliest(),对各任务最早结束时间进行计算;
并由统计任务数,判断图是否连通;
TopSort_Latest(),对各任务最晚开始时间进行计算;
PrintKeyRoute()函数挨个输出关键任务;
int main(){
int a,b;
scanf("%d %d",&N,&M);
//初始化拓扑图A以及各边机动值
for(int i=0;i<N;i++)
for(int j=0;j<N;j++)
D[i][j]=A[i][j]=INFINITY;
//得到拓扑图A
for(int i=0;i<M;i++)
{
scanf("%d %d",&a,&b);
scanf("%d",&A[--a][--b]); //编号从1开始,保存从0保存
}
if(!TopSort_Earliest())
printf("0\n");
else{
printf("%d\n",ECT);
TopSort_Latest();
PrintKeyRoute();
}
}
2.getMax函数
得到Earliest中的最大值,并保存下标;
终点的最早结束时间为每个点的最早结束时间的最大值;
此找到终点并保存最大值,用于第一个输出;
int getMax(int arr[]) //得到列表中最大的元素
{
int max=0;
for (int i=0;i<N;i++)
if(max<arr[i])
{
max=arr[i]; //找到最大值的值
idx=i; //找到最大值的编号
}
return max;
}
3.Earliest函数
计算有向图A的入度;
将入度为0的结点入队;
进入循环:
队头出队,记录任务数cnt++,
遍历堆头结点后面相邻的任务完成结点,若当前结点加任务时间大于下一个结点的开始时间,更新下一个结点的开始时间为大值;
并且将入度–后为0的结点入队;
int TopSort_Earliest(){
int V,cnt = 0, Indegree[MAX] = {0};
queue<int> q;
//计算各节点入度
for(int i=0;i<N;i++)
for(int j=0;j<N;j++)
if(A[i][j]!=INFINITY)
Indegree[j]++;
//找到入度为0的入队
for(int i=0;i<N;i++)
if(Indegree[i]==0)
q.push(i);
while(!q.empty()){
V = q.front(); //得到队头元素
q.pop();
cnt++; //记录结点数
for(int j=0;j<N;j++)
if(A[V][j]!=INFINITY){ //找到V后面的边
if(EarliestTime[V]+A[V][j]>EarliestTime[j]){
//取最大的完成时间
EarliestTime[j]=EarliestTime[V]+A[V][j];
}
if(--Indegree[j]==0)
//若该相邻边入度为0,入队
q.push(j);
}
}
ECT=getMax(EarliestTime); //找到最大元素——即最少总时间
if(cnt!=N) return 0; //代表图不连通
else return 1;
}
// Earlist已算出每个活动的最晚时间
最后getmax得到终点的下标和最大值以及用cnt判断图是否连通。
4.Latest函数
计算每个结点的最晚开始时间;
由结束顶点idx回推;
计算每个结点的出度,将出度为0的入队;
初始化结束顶点的最早结束时间;
开始循环:
挨个出队,遍历该结点的每一个前驱结点,前一个结点的开始时间取 当前节点的开始时间减去前一个任务的花费时间 的最小值,即开始的越早越好;
必须用<=,只<的话只能算一条关键路径,<=才能算出所有的关键路径(错误原因)
除此之外,需要D数组计算机动时间,即前一个结点的开始时间+任务时间刚好=当前结点的开始时间,则该任务无机动时间;
最后将删除该点后出度为0的结点入队;
void TopSort_Latest(){
int V,Outdegree[MAX] = {0}; //出度初始化为0
queue<int> q;
//计算各节点的出度
for(int i=0; i<N; i++)
for(int j=0;j<N;j++)
if(A[i][j]!=INFINITY)
Outdegree[i]++; //计算出度
//出度为0的入队
for(int i=0;i<N;i++)
if(Outdegree[i]==0)
q.push(i);
//初始化latesttime
for(int i=0;i<N;i++)
LatestTime[i]=INFINITY;
LatestTime[idx]=ECT; //idx为最后一个活动的编号;ECT为该点完成的时间
//从后往前推
while(!q.empty())
{
V=q.front();
q.pop();
//cnt++ 以得出图是否联通,cnt无用
for(int j=0;j<N;j++)
if(A[j][V]!=INFINITY){ //找到V前面的结点
if(LatestTime[V]-A[j][V]<=LatestTime[j]){
//找到最晚开始的时间,即前面的点最早需要开始的时间
//必须用<=,只<的话只能算一条关键路径,<=才能算出所有的关键路径(错误原因)
LatestTime[j]=LatestTime[V]-A[j][V];
D[j][V]=LatestTime[V]-EarliestTime[j]-A[j][V];
//D为机动时间:即V的最晚开始时间-(j的最早开始时间+j到V所花的时间)
//j的最早开始时间+j到v所花的时间若等于v的最晚开始时间,则说明
//从j到v没有机动时间,即j-v是一条可输出的路径
}
if(--Outdegree[j]==0)
q.push(j);
}
}
}
5.输出函数
void PrintKeyRoute()
{
for(int i=0;i<N;i++) //任务开始的交接点编号小者优先
for(int j=N-1;j>=0;j--) //起点编号相同时,与输入时任务的顺序相反。
if(D[i][j]==0)
printf("%d->%d\n",i+1,j+1);
}
总结
关键路径——机动时间为0的任务路径,可以有多条;
求关键路径需要先求最早结束时间,再求最晚开始时间时间;
当前点结束时间+该点开始的任务时间=下一个任务开始时间,则说明该任务机动时间为0,即可作为关键点输出;