假定一个工程项目由一组子任务构成,子任务之间有的可以并行执行,有的必须在完成了其它一些子任务后才能执行。“任务调度”包括一组子任务、以及每个子任务可以执行所依赖的子任务集。
比如完成一个专业的所有课程学习和毕业设计可以看成一个本科生要完成的一项工程,各门课程可以看成是子任务。有些课程可以同时开设,比如英语和C程序设计,它们没有必须先修哪门的约束;有些课程则不可以同时开设,因为它们有先后的依赖关系,比如C程序设计和数据结构两门课,必须先学习前者。
但是需要注意的是,对一组子任务,并不是任意的任务调度都是一个可行的方案。比如方案中存在“子任务A依赖于子任务B,子任务B依赖于子任务C,子任务C又依赖于子任务A”,那么这三个任务哪个都不能先执行,这就是一个不可行的方案。
任务调度问题中,如果还给出了完成每个子任务需要的时间,则我们可以算出完成整个工程需要的最短时间。在这些子任务中,有些任务即使推迟几天完成,也不会影响全局的工期;但是有些任务必须准时完成,否则整个项目的工期就要因此延误,这种任务就叫“关键活动”。
请编写程序判定一个给定的工程项目的任务调度是否可行;如果该调度方案可行,则计算完成整个工程项目需要的最短时间,并输出所有的关键活动。
输入格式:
输入第1行给出两个正整数N(≤100)和M,其中N是任务交接点(即衔接相互依赖的两个子任务的节点,例如:若任务2要在任务1完成后才开始,则两任务之间必有一个交接点)的数量。交接点按1~N编号,M是子任务的数量,依次编号为1~M。随后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
拓扑排序的变形,具体代码实现如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MaxVertexNum 105 /* 最大顶点数设为105 */
typedef int Vertex; /* 用顶点下标表示顶点,为整型 */
typedef int WeightType; /* 边的权值设为整型 */
typedef int DataType; /* 顶点存储的数据类型设为整型 */
/* 边的定义 */
typedef struct ENode *PtrToENode;
struct ENode{
Vertex V1, V2; /* 有向边<V1, V2> */
WeightType Weight; /* 权重 */
};
typedef PtrToENode Edge;
/* 邻接点的定义 */
typedef struct AdjVNode *PtrToAdjVNode;
struct AdjVNode{
Vertex AdjV; /* 邻接点下标 */
WeightType Weight; /* 边权重 */
PtrToAdjVNode Next; /* 指向下一个邻接点的指针 */
};
/* 顶点表头结点的定义 */
typedef struct Vnode{
PtrToAdjVNode FirstEdge;/* 边表头指针 */
DataType Earliest; /* 存顶点的数据 */
DataType Latest;
} AdjList[MaxVertexNum]; /* AdjList是邻接表类型 */
/* 图结点的定义 */
typedef struct GNode *PtrToGNode;
struct GNode{
int Nv; /* 顶点数 */
int Ne; /* 边数 */
AdjList G; /* 邻接表 */
};
typedef PtrToGNode LGraph; /* 以邻接表方式存储的图类型 */
LGraph BuildGraph();
bool TopSort( LGraph Graph, Vertex TopOrder[] );
void Critical_path(LGraph Graph);
int main()
{
LGraph graph;
Vertex TopOrder[MaxVertexNum];
int i,max = 0; //所有检查点最早完成时间的最大值
Vertex V;
PtrToAdjVNode W;
graph = BuildGraph();
if( !TopSort(graph,TopOrder) )
printf("0\n");
else{
for(i=0; i<graph->Nv; i++){
if(graph->G[i].Earliest > max){
max = graph->G[i].Earliest;
}
}
printf("%d\n",max);
for(i=0; i<graph->Nv; i++)
graph->G[i].Latest = max; //初始化每个顶点的最晚完成时间为max天
Critical_path( graph );
for(i=0; i<graph->Nv; i++){
//V = TopOrder[i];
for (W=graph->G[i].FirstEdge; W; W=W->Next){
if( graph->G[i].Earliest+W->Weight == graph->G[W->AdjV].Latest ){
printf("%d->%d\n",i+1,W->AdjV+1);
}
}
}
}
return 0;
}
LGraph CreateGraph( int VertexNum )
{ /* 初始化一个有VertexNum个顶点但没有边的图 */
Vertex V;
LGraph Graph;
Graph = (LGraph)malloc( sizeof(struct GNode) ); /* 建立图 */
Graph->Nv = VertexNum;
Graph->Ne = 0;
/* 初始化邻接表头指针 */
/* 注意:这里默认顶点编号从0开始,到(Graph->Nv - 1) */
for (V=0; V<Graph->Nv; V++)
Graph->G[V].FirstEdge = NULL;
return Graph;
}
void InsertEdge( LGraph Graph, Edge E )
{
PtrToAdjVNode NewNode;
PtrToAdjVNode W;
/* 插入边 <V1, V2> */
/* 为V2建立新的邻接点 */
NewNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
NewNode->AdjV = E->V2;
NewNode->Weight = E->Weight;
/* 将V2插入V1的表头 */
NewNode->Next = Graph->G[E->V1].FirstEdge;
Graph->G[E->V1].FirstEdge = NewNode;
}
LGraph BuildGraph()
{
LGraph Graph;
Edge E;
Vertex V;
int Nv, i;
scanf("%d", &Nv); /* 读入顶点个数 */
Graph = CreateGraph(Nv); /* 初始化有Nv个顶点但没有边的图 */
scanf("%d", &(Graph->Ne)); /* 读入边数 */
if ( Graph->Ne != 0 ) { /* 如果有边 */
E = (Edge)malloc( sizeof(struct ENode) ); /* 建立边结点 */
/* 读入边,格式为"起点 终点 权重",插入邻接表中 */
for (i=0; i<Graph->Ne; i++) {
scanf("%d %d %d", &E->V1, &E->V2, &E->Weight);
//注意:这里默认顶点编号从0开始,到(Graph->Nv-1)
E->V1--;
E->V2--;
InsertEdge( Graph, E );
}
}
for (V=0; V<Graph->Nv; V++)
Graph->G[V].Earliest = 0; //初始化每个顶点的最早完成时间为0天
return Graph;
}
//int compare (const void * a, const void * b)
//{
// return ( *(int*)a - *(int*)b );
//}
/* 邻接表存储 - 拓扑排序算法 */
bool TopSort( LGraph Graph, Vertex TopOrder[] )
{ /* 对Graph进行拓扑排序, TopOrder[]顺序存储排序后的顶点下标 */
int Indegree[MaxVertexNum], cnt;
Vertex V;
PtrToAdjVNode W;
Vertex queue[MaxVertexNum]; //结点队列
int head = 0,tail = 0; //队列头尾指针
//int i,j;
//int sort[MaxVertexNum];
/* 初始化Indegree[] */
for (V=0; V<Graph->Nv; V++)
Indegree[V] = 0;
/* 遍历图,得到Indegree[] */
for (V=0; V<Graph->Nv; V++)
for (W=Graph->G[V].FirstEdge; W; W=W->Next)
Indegree[W->AdjV]++; /* 对有向边<V, W->AdjV>累计终点的入度 */
/* 将所有入度为0的顶点入列 */
for (V=0; V<Graph->Nv; V++)
if ( Indegree[V]==0 )
queue[tail++] = V;
/* 下面进入拓扑排序 */
cnt = 0;
while( head < tail ){
V = queue[head++]; /* 弹出一个入度为0的顶点 */
TopOrder[cnt++] = V; /* 将之存为结果序列的下一个元素 */
/* 对V的每个邻接点W->AdjV */
//j = 0;
//memset(sort,0,sizeof(sort));
for ( W=Graph->G[V].FirstEdge; W; W=W->Next ){
/*W->Adjv结点处的最早完成时间为(每个前面必须完成结点的最早完成时间+边的权重)的最大值*/
if( Graph->G[V].Earliest+W->Weight > Graph->G[W->AdjV].Earliest)
Graph->G[W->AdjV].Earliest = Graph->G[V].Earliest+W->Weight;
if ( --Indegree[W->AdjV] == 0 )/* 若删除V使得W->AdjV入度为0 */
queue[tail++] = W->AdjV; /* 则该顶点入列 */
}
// qsort(sort,j,sizeof(int),compare);
// for(i=0; i<j; i++){
// queue[tail++] = sort[i]; /* 则该顶点入列 */
// }
} /* while结束*/
if ( cnt != Graph->Nv )
return false; /* 说明图中有回路, 返回不成功标志 */
else
return true;
}
void Critical_path(LGraph Graph)
{
int Outdegree[MaxVertexNum];
Vertex V;
PtrToAdjVNode W;
Vertex queue[MaxVertexNum]; //结点队列
int head = 0,tail = 0; //队列头尾指针
int i;
/* 初始化Outdegree[] */
for (V=0; V<Graph->Nv; V++)
Outdegree[V] = 0;
/* 遍历图,得到Outdegree[] */
for (V=0; V<Graph->Nv; V++)
for (W=Graph->G[V].FirstEdge; W; W=W->Next)
Outdegree[V]++; /* 对有向边<V, W->AdjV>累计起点的出度 */
/* 将所有出度为0的顶点入列 */
for (V=0; V<Graph->Nv; V++)
if ( Outdegree[V]==0 )
queue[tail++] = V;
while( head < tail ){
V = queue[head++]; /* 弹出一个出度为0的顶点 */
for(i=0; i<Graph->Nv; i++){
for ( W=Graph->G[i].FirstEdge; W; W=W->Next ){
/*对V的每个前驱节点*/
if(W->AdjV == V){
/*i结点处的最晚完成时间为
(其每个后继结点的最晚完成时间-边的权重)中的最小值*/
if( Graph->G[V].Latest-W->Weight < Graph->G[i].Latest)
Graph->G[i].Latest = Graph->G[V].Latest-W->Weight;
if ( --Outdegree[i] == 0 )/* 若删除V使得i出度为0 */
queue[tail++] = i; /* 则该顶点入列 */
}
}
}
}
}