浙大数据结构第八周之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;
}
相关推荐
自由的dream28 分钟前
0-1背包问题
算法
2401_8572979134 分钟前
招联金融2025校招内推
java·前端·算法·金融·求职招聘
良月澪二2 小时前
CSP-S 2021 T1廊桥分配
算法·图论
wangyue43 小时前
c# 线性回归和多项式拟合
算法
&梧桐树夏3 小时前
【算法系列-链表】删除链表的倒数第N个结点
数据结构·算法·链表
QuantumStack3 小时前
【C++ 真题】B2037 奇偶数判断
数据结构·c++·算法
今天好像不上班3 小时前
软件验证与确认实验二-单元测试
测试工具·算法
wclass-zhengge4 小时前
数据结构篇(绪论)
java·数据结构·算法
Dylanioucn4 小时前
【分布式微服务云原生】探索Redis:数据结构的艺术与科学
数据结构·redis·分布式·缓存·中间件
何事驚慌4 小时前
2024/10/5 数据结构打卡
java·数据结构·算法