浙大数据结构第八周之08-图9 关键活动

题目详情:

假定一个工程项目由一组子任务构成,子任务之间有的可以并行执行,有的必须在完成了其它一些子任务后才能执行。"任务调度"包括一组子任务、以及每个子任务可以执行所依赖的子任务集。

比如完成一个专业的所有课程学习和毕业设计可以看成一个本科生要完成的一项工程,各门课程可以看成是子任务。有些课程可以同时开设,比如英语和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>
#define MAX_NODE_NUMS 105
#define TRUE 1
#define FALSE 0
#define NONE -1
#define INFINITY 10000
typedef int bool;
/*实现循环队列的数据结构*/
typedef struct QueueNode QueueNode;
typedef QueueNode* Queue;
struct QueueNode {
    int Head, Rear, Size;
    int Data[MAX_NODE_NUMS];
};
void InitQueue(Queue* q) {
    (*q)->Head = 0;
    (*q)->Rear = -1;
    (*q)->Size = 0;
    for(int i = 0; i < MAX_NODE_NUMS; i++) {
        (*q)->Data[i] = NONE;
    }
    return;
}
bool IsQueueFull(Queue* q) {
    if((*q)->Size == MAX_NODE_NUMS) {
        return TRUE;
    }
    else {
        return FALSE;
    }
}
bool IsQueueEmpty(Queue* q) {
    if((*q)->Size == 0) {
        return TRUE;
    }
    else {
        return FALSE;
    }
}
int Dequeue(Queue* q) {
    if(!IsQueueEmpty(q)) {
        int ret = (*q)->Data[(*q)->Head];
        (*q)->Head = ((*q)->Head + 1) % MAX_NODE_NUMS;
        (*q)->Size--;
        (*q)->Data[(*q)->Head - 1] = NONE;
        return ret;
    }
}
void Enqueue(Queue* q, int data) {
    if(!IsQueueFull(q)) {
        (*q)->Data[((*q)->Rear + 1) % MAX_NODE_NUMS] = data;
        (*q)->Rear = ((*q)->Rear + 1) % MAX_NODE_NUMS;
        (*q)->Size++;
        return;
    }
}
/*实现通过邻接表建图的数据结构*/
typedef struct EdgeNode EdgeNode;
typedef EdgeNode* PToEdge;
struct EdgeNode {
    int Start, End, Weight;
};
typedef struct AdjVNode AdjVNode;
typedef AdjVNode* PToAdjVNode;
struct AdjVNode {
    int Index;
    int Weight;
    PToAdjVNode Next;
};
typedef struct HeadNode HeadNode;
struct HeadNode {
    int Data;
    PToAdjVNode FirstEdge;
};
typedef struct ListGraphNode ListGraphNode;
typedef ListGraphNode* ListGraph;
struct ListGraphNode {
    int VertexNums, EdgeNums;
    HeadNode Head[MAX_NODE_NUMS];
};
ListGraph CreateEmptyGraph(int vertexNums) {
    ListGraph graph = (ListGraph)malloc(sizeof(ListGraphNode));
    graph->VertexNums = vertexNums;
    for(int i = 0; i <= graph->VertexNums; i++) {
        graph->Head[i].FirstEdge = NULL;
    }
    return graph;
}
void InsertEdge(ListGraph graph, PToEdge edge) {
    PToAdjVNode newNode = (PToAdjVNode)malloc(sizeof(AdjVNode));
    newNode->Index = edge->End;
    newNode->Weight = edge->Weight;
    newNode->Next = graph->Head[edge->Start].FirstEdge;
    graph->Head[edge->Start].FirstEdge = newNode;
    return;
}
void BuildTwoGraph(int vertexNums, int edgeNums, ListGraph* forwardGraph, ListGraph* backwardGraph) {
    (*forwardGraph) = CreateEmptyGraph(vertexNums);
    (*backwardGraph) = CreateEmptyGraph(vertexNums);
    for(int i = 0; i < edgeNums; i++) {
        int start, end, weight;
        scanf("%d %d %d", &start, &end, &weight);
        PToEdge forwardEdge = (PToEdge)malloc(sizeof(EdgeNode));
        PToEdge backwardEdge = (PToEdge)malloc(sizeof(EdgeNode));
        forwardEdge->Start = start; backwardEdge->Start = end;
        forwardEdge->End = end; backwardEdge->End = start;
        forwardEdge->Weight = backwardEdge->Weight = weight;
        InsertEdge(*forwardGraph, forwardEdge);
        InsertEdge(*backwardGraph, backwardEdge);
        free(forwardEdge); free(backwardEdge);
    }
    return;
}   
/*删除图*/
void DeleteGraph(ListGraph graph) {
    for(int i = 0; i < graph->VertexNums; i++) {
        PToAdjVNode p = graph->Head[i].FirstEdge;
        while(p) {
            PToAdjVNode q = p;
            p = p->Next;
            free(q);
        }
    }
    free(graph);
}
/*实现拓扑排序,记录关键路径*/
int InDegree[MAX_NODE_NUMS];
int OutDegree[MAX_NODE_NUMS];
int Earliest[MAX_NODE_NUMS];
int Latest[MAX_NODE_NUMS];
void CriticalPath(ListGraph forwardGraph, ListGraph backwardGraph) {
    //对正向图进行拓扑排序
    int Vcount = 0;
    Queue forwardQ = (Queue)malloc(sizeof(QueueNode));
    InitQueue(&forwardQ);
    //初始化入度
    for(int i = 1; i <= forwardGraph->VertexNums; i++) {
        for(PToAdjVNode p = forwardGraph->Head[i].FirstEdge; p; p = p->Next) {
            InDegree[p->Index]++;
        }
    }
    //将入度为0的节点入队
    for(int i = 1; i <= forwardGraph->VertexNums; i++) {
        if(InDegree[i] == 0) {
            Enqueue(&forwardQ, i);
        }
    }

    while(!IsQueueEmpty(&forwardQ)) {
        int vertex = Dequeue(&forwardQ);
        Vcount++;
        for(PToAdjVNode p = forwardGraph->Head[vertex].FirstEdge; p; p = p->Next) {
            if(Earliest[p->Index] < Earliest[vertex] + p->Weight) {
                Earliest[p->Index] = Earliest[vertex] + p->Weight;
            }
            if(--InDegree[p->Index] == 0) {
                Enqueue(&forwardQ, p->Index);
            }
        }
    }

    if(Vcount != forwardGraph->VertexNums) {
        printf("0");
        free(forwardQ);
        return;
    }
    else {
        int max = 0;
        for(int i = 1; i <= forwardGraph->VertexNums; i++) {
            if(max < Earliest[i]) {
                max = Earliest[i];
            }
        }
        //初始化最晚完成时间
        for(int i = 1; i <= backwardGraph->VertexNums; i++) {
            Latest[i] = max;
        }

        //初始化出度
        for(int i = 1; i <= backwardGraph->VertexNums; i++) {
            for(PToAdjVNode p = backwardGraph->Head[i].FirstEdge; p; p = p->Next) {
                OutDegree[p->Index]++;
            }
        }
        Queue backwardQ = (Queue)malloc(sizeof(QueueNode));
        InitQueue(&backwardQ);
        //将出度为0的节点入队
        for(int i = 1; i <= backwardGraph->VertexNums; i++) {
            if(OutDegree[i] == 0) {
                Enqueue(&backwardQ, i);
            }
        }
        while(!IsQueueEmpty(&backwardQ)) {
            int vertex = Dequeue(&backwardQ);
            for(PToAdjVNode p = backwardGraph->Head[vertex].FirstEdge; p; p = p->Next) {
                if(Latest[p->Index] > Latest[vertex] - p->Weight) { //这一步是什么意思
                    Latest[p->Index] = Latest[vertex] - p->Weight;
                }
                if(--OutDegree[p->Index] == 0) {
                    Enqueue(&backwardQ, p->Index);
                }
            }
        }
        printf("%d\n", max);
        for(int i = 1; i <= forwardGraph->VertexNums; i++) {
            for(PToAdjVNode p = forwardGraph->Head[i].FirstEdge; p; p = p->Next) {
                if(Latest[p->Index] - p->Weight == Earliest[i]) {
                    printf("%d->%d\n", i, p->Index);
                }
            }
        }
        free(backwardQ);
    }
    free(forwardQ);
    return;
}

int main() {
    int vertexNums, edgeNums;
    scanf("%d %d", &vertexNums, &edgeNums);
    ListGraph forwardGraph, backwardGraph;
    BuildTwoGraph(vertexNums, edgeNums, &forwardGraph, &backwardGraph);
    CriticalPath(forwardGraph, backwardGraph);
    DeleteGraph(forwardGraph); DeleteGraph(backwardGraph);
    return 0;
}
相关推荐
ChoSeitaku3 分钟前
链表循环及差集相关算法题|判断循环双链表是否对称|两循环单链表合并成循环链表|使双向循环链表有序|单循环链表改双向循环链表|两链表的差集(C)
c语言·算法·链表
Fuxiao___12 分钟前
不使用递归的决策树生成算法
算法
我爱工作&工作love我17 分钟前
1435:【例题3】曲线 一本通 代替三分
c++·算法
白-胖-子1 小时前
【蓝桥等考C++真题】蓝桥杯等级考试C++组第13级L13真题原题(含答案)-统计数字
开发语言·c++·算法·蓝桥杯·等考·13级
workflower1 小时前
数据结构练习题和答案
数据结构·算法·链表·线性回归
好睡凯1 小时前
c++写一个死锁并且自己解锁
开发语言·c++·算法
Sunyanhui11 小时前
力扣 二叉树的直径-543
算法·leetcode·职场和发展
一个不喜欢and不会代码的码农1 小时前
力扣105:从先序和中序序列构造二叉树
数据结构·算法·leetcode
前端郭德纲1 小时前
浏览器是加载ES6模块的?
javascript·算法
SoraLuna1 小时前
「Mac玩转仓颉内测版10」PTA刷题篇1 - L1-001 Hello World
算法·macos·cangjie