关键路径应用

【问题描述】给定一个项目,项目的各个子任务的完成时间以及子任务之间的依赖关系已给出,采用有向图表示。请编写程序判定一个给定的工程项目的任务调度是否可行;如果该调度方案可行,则计算完成整个工程项目需要的最短时间,并输出所有的关键活动。

【输入形式】

7 8 //结点数(结点编号从1开始连续编码),边的数量
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
如果任务不可调度,则输出:
0
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
// 
#define MAX_VERTEX_NUM 100 // 最大顶点数

// 定义邻接表结点
typedef struct ArcNode {
    int adjvex;                 // 弧头结点的位置
    int weight;                 // 边的权重,即活动所需时间
    struct ArcNode* nextarc;    // 指向下一条弧的指针
} ArcNode;

// 定义顶点表结点
typedef struct VNode {
    int indegree;              // 结点入度
    int earliest;              // 该活动最早开始时间
    int latest;                // 该活动最晚开始时间
    ArcNode* firstarc;         // 指向第一条依附该结点的弧
} VNode, AdjList[MAX_VERTEX_NUM];

// 定义图结构
typedef struct {
    AdjList vertices;          // 邻接表
    int vexnum, arcnum;        // 图的顶点数和弧数
} ALGraph;

// 初始化图
void InitGraph(ALGraph* G, int n) {
    G->vexnum = n;
    G->arcnum = 0;
    for (int i = 1; i <= n; i++) {
        G->vertices[i].indegree = 0;
        G->vertices[i].earliest = 0;
        G->vertices[i].latest = INT_MAX; // 初始化最晚开始时间为无穷大
        G->vertices[i].firstarc = NULL;
    }
}

// 插入边(使用头插法)
void InsertArc(ALGraph* G, int tail, int head, int weight) {
    ArcNode* arcNode = (ArcNode*)malloc(sizeof(ArcNode));
    arcNode->adjvex = head;
    arcNode->weight = weight;
    arcNode->nextarc = G->vertices[tail].firstarc;
    G->vertices[tail].firstarc = arcNode;
    G->vertices[head].indegree++; // 增加终点的入度
    G->arcnum++;
}

// 拓扑排序
int TopologicalSort(ALGraph* G, int* etv) {
    int count = 0; // 记录已经输出的顶点数
    int stack[MAX_VERTEX_NUM]; // 存储入度为0的顶点
    int top = -1; // 栈顶指针

    for (int i = 1; i <= G->vexnum; i++) {
        if (G->vertices[i].indegree == 0) { // 将入度为0的顶点入栈
            stack[++top] = i;
        }
    }

    while (top != -1) {
        int v = stack[top--]; // 出栈一个顶点
        count++;

        ArcNode* p = G->vertices[v].firstarc;
        while (p != NULL) {
            int w = p->adjvex; // 弧头结点
            if (--G->vertices[w].indegree == 0) { // 入度减1后变为0的顶点入栈
                stack[++top] = w;
            }
            if (etv[v] + p->weight > etv[w]) {
                etv[w] = etv[v] + p->weight; // 更新最早开始时间
            }
            p = p->nextarc;
        }
    }

    if (count != G->vexnum) { // 如果存在环路,说明任务不可调度
        return 0;
    } else {
        return 1;
    }
}

// 关键路径计算
void CriticalPath(ALGraph* G) {
    int etv[MAX_VERTEX_NUM]; // 事件最早发生时间
    int ltv[MAX_VERTEX_NUM]; // 事件最晚发生时间

    for (int i = 1; i <= G->vexnum; i++) {
        etv[i] = 0;
    }

    if (!TopologicalSort(G, etv)) { // 执行拓扑排序,判断任务是否可调度
        printf("0\n"); // 任务不可调度
        return;
    }

    // 初始化ltv数组
    for (int i = 1; i <= G->vexnum; i++) {
        ltv[i] = etv[G->vexnum];
    }

    int stack[MAX_VERTEX_NUM]; // 存储关键路径上的顶点
    int top = -1; // 栈顶指针

    for (int i = G->vexnum; i >= 1; i--) {
        ArcNode* p = G->vertices[i].firstarc;
        while (p != NULL) {
            int k = p->adjvex; // 弧头结点
            if (ltv[k] - p->weight < ltv[i]) {
                ltv[i] = ltv[k] - p->weight; // 更新最晚开始时间
            }
            p = p->nextarc;
        }

        if (etv[i] == ltv[i]) { // 找到关键活动,将顶点入栈
            stack[++top] = i;
        }
    }

    printf("%d\n", etv[G->vexnum]); // 输出完成整个工程项目所需最短时间

    while (top != -1) { // 输出关键活动
        int v = stack[top--];
        ArcNode* p = G->vertices[v].firstarc;
        while (p != NULL) {
            int k = p->adjvex; // 弧头结点
            if (etv[v] == ltv[k] - p->weight) {
                printf("%d %d\n", v, k); // 输出关键路径上的边
            }
            p = p->nextarc;
        }
    }
}

int main() {
    int n, m; // 结点数和边的数量

    scanf("%d %d", &n, &m);

    ALGraph G;
    InitGraph(&G, n);

    for (int i = 0; i < m; i++) {
        int tail, head, weight;
        scanf("%d %d %d", &tail, &head, &weight);
        InsertArc(&G, tail, head, weight);
    }

    CriticalPath(&G);

    return 0;
}

       以上代码使用了关键路径算法(Critical Path Method,CPM)来计算项目的关键路径。关键路径算法基于拓扑排序动态规划的思想。

具体算法步骤如下:

  1. 构建有向无环图:根据输入的边信息,构建有向无环图(DAG),其中每个结点表示一个任务,边表示任务之间的依赖关系。

  2. 拓扑排序:通过拓扑排序,确定每个任务的最早开始时间(Earliest Start Time,etv)。拓扑排序从入度为0的结点开始,逐层移除入度为0的结点,并更新其后继结点的最早开始时间。

  3. 计算最晚开始时间:逆向遍历图,计算每个任务的最晚开始时间(Latest Start Time,ltv)。从最后一个任务开始,根据后继任务的最晚开始时间和边的权重,逐层向前计算每个任务的最晚开始时间。

  4. 判断可调度性:如果拓扑排序完成后,仍存在入度不为0的结点,说明图中存在环路,任务不可调度。

  5. 输出关键路径:根据最早开始时间和最晚开始时间的比较,找到关键活动(即etv[i] == ltv[i]的任务),输出关键路径上的边。

该算法的时间复杂度为O(n + m),其中n为顶点数,m为边数。

拓扑排序的方法:

1.在有向图中选择一个没有前驱的顶点输出;

2.从图中删除该顶点和所以以它为尾的弧。

重复以上两步,直至全部顶点均已输出。

注:

拓扑排序是一种对有向无环图(DAG)进行排序的算法。它通过分析图中各个结点之间的依赖关系,确定结点的执行顺序,使得所有的依赖关系得以满足。

拓扑排序主要用来解决以下问题:

  1. 任务调度:在项目管理中,任务之间存在不同的先后关系和依赖关系,拓扑排序可以确定任务的执行顺序,从而合理安排任务的时间表,保证项目按计划进行。

  2. 课程安排:在学校或大学的课程安排中,课程之间存在先修课程和依赖关系,拓扑排序可以确定学习课程的顺序,避免学生学习未掌握的前置知识。

  3. 编译顺序:在编译程序时,源代码文件之间可能存在相互引用和依赖关系,拓扑排序可以确定编译的顺序,保证依赖的文件先编译,避免出现错误。

  4. 任务优先级:在任务管理中,不同任务可能具有不同的优先级和依赖关系,拓扑排序可以确定任务的优先级顺序,帮助选择最重要或最紧急的任务。

总而言之,拓扑排序适用于有向无环图,并且在任务之间存在依赖关系的场景中,可以帮助确定结点的执行顺序和解决相关问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值