浙大数据结构第八周之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;
}
相关推荐
mit6.8241 分钟前
[数据结构] LRU Cache | List&Map 实现
算法
yuanbenshidiaos10 分钟前
数据结构----链表头插中插尾插
网络·数据结构·链表
逊嘘20 分钟前
【Java数据结构】LinkedList
java·开发语言·数据结构
Schwertlilien41 分钟前
图像处理-Ch1-数字图像基础
图像处理·人工智能·算法
程序员一诺42 分钟前
【深度学习】嘿马深度学习笔记第10篇:卷积神经网络,学习目标【附代码文档】
人工智能·python·深度学习·算法
刚学HTML2 小时前
leetcode 05 回文字符串
算法·leetcode
Yan.love2 小时前
开发场景中Java 集合的最佳选择
java·数据结构·链表
AC使者3 小时前
#B1630. 数字走向4
算法
冠位观测者3 小时前
【Leetcode 每日一题】2545. 根据第 K 场考试的分数排序
数据结构·算法·leetcode
古希腊掌管学习的神3 小时前
[搜广推]王树森推荐系统笔记——曝光过滤 & Bloom Filter
算法·推荐算法