假定一个工程项目由一组子任务构成,子任务之间有的可以并行执行,有的必须在完成了其它一些子任务后才能执行。“任务调度”包括一组子任务、以及每个子任务可以执行所依赖的子任务集。
比如完成一个专业的所有课程学习和毕业设计可以看成一个本科生要完成的一项工程,各门课程可以看成是子任务。有些课程可以同时开设,比如英语和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
自己写的AC代码:
//向前和向后使用拓扑排序
#include<iostream>
#include<stdio.h>
#include<queue>
#define max1 101
#define INF 10000000
using namespace std;
int n,m;
int cnt=0;
queue<int>q1,q2;
int maxcost;
int early[max1];
int late[max1];
int indegree[max1];
int outdegree[max1];
int p[max1][max1];
void TopsortEarly();
void TopsortLate();
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
late[i]=INF;indegree[i]=0;outdegree[i]=0;early[i]=0;
for(int j=1;j<=n;j++){
p[i][j]=-1;
}
}
int k1,k2,k3;
for(int i=1;i<=m;i++){
scanf("%d %d %d",&k1,&k2,&k3);
p[k1][k2]=k3;
indegree[k2]++;
outdegree[k1]++;
}
TopsortEarly();
if(cnt<n){
return 0;
}
TopsortLate();
for(int i=1;i<=n;i++){
if(late[i]==early[i]){
//j要从后面往前面倒,否则会漏掉
for(int j=n;j>=1;j--){
//最后这一步的条件判断非常重要,i,j均为关键路径,且相邻,并且j的最晚-中间的活动时间=i的最早时间
if(late[j]==early[j]&&p[i][j]>=0&&late[j]-p[i][j]==early[i])printf("%d->%d\n",i,j);
}
}
//printf("early[%d]=%d,late[%d]=%d\n",i,early[i],i,late[i]);
}
return 0;
}
void TopsortEarly(){
for(int i=1;i<=n;i++){
if(indegree[i]==0){
q1.push(i);
early[i]=0;
cnt++;
}
}
while(!q1.empty()){
int k=q1.front();q1.pop();
for(int i=1;i<=n;i++){
if(p[k][i]>=0){
early[i]=max(early[i],early[k]+p[k][i]);
indegree[i]--;
if(indegree[i]==0){
q1.push(i);cnt++;
}
}
}
}
if(cnt<n){
cout<<"0"<<endl;
}else{
int maxx=early[1];
for(int i=2;i<=n;i++){ //不能简单的假设n就是最大的,也就是最后完成,需要再进一步求最大的是哪个
if(early[i]>maxx){
maxx=early[i];
}
}
cout<<maxx<<endl;
maxcost=maxx;
}
}
void TopsortLate(){
for(int i=n;i>=1;i--){
if(outdegree[i]==0){
q2.push(i);
late[i]=maxcost;
}
}
while(!q2.empty()){
int k=q2.front();q2.pop();
for(int j=n;j>=1;j--){
if(p[j][k]>=0){
late[j]=min(late[j],late[k]-p[j][k]);
outdegree[j]--;
if(outdegree[j]==0){
q2.push(j);
}
}
}
}
}
网上一个小伙伴用链表求解,运行时间更短,想进一步学习
#include<iostream>
#include<queue>
#include<stack>
#include<map>
#include<utility>
using namespace std;
#define Maxsize 101
#define Inf 65535
typedef int Vertex;
typedef int WeightType;
typedef struct LNode* PtrToLNode;
typedef struct VNode {
PtrToLNode FirstEdge;
WeightType Earliest;
WeightType Latest;
int InDegree;
VNode():FirstEdge(NULL),Earliest(0),Latest(Inf), InDegree(0){}
}PtrToLNodeArr[Maxsize];
struct LNode {
Vertex V;
WeightType Weight;
WeightType Delay;
PtrToLNode Next;
};
typedef struct GraphNode* Graph;
struct GraphNode {
int Nv;
int Ne;
PtrToLNodeArr heads;
};
Graph BuildGraph(int N, int M) {
Graph G = new GraphNode;
G->Nv = N;
G->Ne = M;
Vertex v1, v2;
WeightType W;
PtrToLNode tmp;
for (int i = 1; i <= M; i++) {
cin >> v1 >> v2 >> W;
tmp = new LNode;
tmp->V = v2;
tmp->Weight = W;
tmp->Next = G->heads[v1].FirstEdge;
G->heads[v1].FirstEdge = tmp;
}
return G;
}
/*以上为建图*/
/*拓扑排序*/
void InDegreeCount(Graph G) {
/*统计入度*/
PtrToLNode tmp;
Vertex V,W;
for (V = 1; V <= G->Nv; V++) {
tmp = G->heads[V].FirstEdge;
while (tmp != NULL) {
W = tmp->V;
G->heads[W].InDegree++;
tmp = tmp->Next;
}
}
}
int TopSorting(Graph G) {
InDegreeCount(G);
queue<Vertex> q;
stack<Vertex> s;
Vertex V,W;//点对象
int res = 0;//结果
PtrToLNode tmp;//边对象指针
for (Vertex V = 1; V <= G->Nv; V++) {//将入度为0的入队列
if (G->heads[V].InDegree == 0) {
q.push(V);
}
}
int Vcount = 0;//用来统计拓扑排序是否失效
while (q.empty() != 1) {
V = q.front();
s.push(V);
q.pop();//弹出V
Vcount++;
for (tmp = G->heads[V].FirstEdge; tmp != NULL; tmp = tmp->Next) {//对V的邻接点进行更新
W = tmp->V;
G->heads[W].InDegree--;//更新入度
if (G->heads[W].InDegree == 0) {
q.push(W);
}
if (G->heads[V].Earliest + tmp->Weight > G->heads[W].Earliest) {
G->heads[W].Earliest = G->heads[V].Earliest + tmp->Weight;//更新W的最早时间
}
}
}
/*判断拓扑排序是否失效,没有失效才继续往下做*/
if (Vcount != G->Nv) {
res = 0;
return res;
}
InDegreeCount(G);//重新更新一下入度,可不做这一步,不影响结果
int MaxTime = 0;
for (Vertex Vtmp = 1; Vtmp <= G->Nv; Vtmp++) {//找到所有节点中的最大的时间
MaxTime = MaxTime > G->heads[Vtmp].Earliest ? MaxTime : G->heads[Vtmp].Earliest;
}
for (Vertex Vtmp = 1; Vtmp <= G->Nv; Vtmp++) {//初始化所有节点的Latest时间为MaxTime
G->heads[Vtmp].Latest = MaxTime;
}
while (s.empty() != 1) {//按照栈的顺序进行反序修改Latest
V = s.top();
s.pop();//反序弹出V
for (tmp = G->heads[V].FirstEdge; tmp != NULL; tmp = tmp->Next) {
W = tmp->V;
if (G->heads[V].Latest > G->heads[W].Latest - tmp->Weight) {
G->heads[V].Latest = G->heads[W].Latest - tmp->Weight;//更新V点的Latest
}
}
}
for (Vertex Vtmp = 1; Vtmp <= G->Nv; Vtmp++) {//对所有边对象更新Delay
tmp = G->heads[Vtmp].FirstEdge;
while (tmp != NULL) {
W = tmp->V;
tmp->Delay = G->heads[W].Latest - (G->heads[Vtmp].Earliest + tmp->Weight);//计算边<v,w>的delay
tmp = tmp->Next;
}
}
return 1;
}
int FindMaxTime(Graph G) {
int MaxTime = 0;
for (Vertex Vtmp = 1; Vtmp <= G->Nv; Vtmp++) {
MaxTime = MaxTime > G->heads[Vtmp].Earliest ? MaxTime : G->heads[Vtmp].Earliest;
}
return MaxTime;
}
void DispRes(int res, Graph G) {
if (res == 0) {
cout << "0";
}
else if (res == 1) {
int MaxTime = FindMaxTime(G);
multimap<Vertex, PtrToLNode> path;
PtrToLNode tmp;//边对象
for (Vertex V = 1; V <= G->Nv; V++) {
tmp = G->heads[V].FirstEdge;
while (tmp != NULL) {
if (tmp->Delay == 0) {//判断是否是关键路径
path.insert(make_pair(V, tmp));
}
tmp = tmp->Next;
}
}
cout << MaxTime << endl;
for (auto pt = path.begin(); pt != path.end(); pt++) {
cout << pt->first << "->" << pt->second->V << endl;
}
}
}
int main() {
int N, M;
cin >> N >> M;
Graph G= BuildGraph(N, M);
int res;
res=TopSorting(G);
DispRes(res, G);
return 0;
}