数据结构与算法之关键路径

关键路径是指工程活动中最长时间路径,也就是各活动所需时间之和最长的路径。在工程管理中,关键路径的计算是为了确定完成整个工程至少需要多长时间,以及哪些活动是工期关键点,需要特别关注和管理。

关键路径算法是一种基于网络图的算法,通过将工程活动抽象为有向图中的节点,边则表示活动之间的先后关系和时间限制。通过计算出图中所有路径所需时间,以及每个节点的最早开始时间和最晚开始时间,可以确定关键路径。

关键路径算法的基本步骤如下:

  1. 构建工程活动有向图,确定所有活动之间的先后关系和时间限制。

  2. 计算出每个节点的最早开始时间和最晚开始时间,其中最早开始时间指的是在不影响整个工程进度的情况下,活动能够最早开始的时间;最晚开始时间则指的是在不延误整个工程完成时间的情况下,活动必须最晚开始的时间。

  3. 计算图中所有路径所需时间,确定关键路径,即路径耗时最长的路径。

  4. 确定关键路径上的活动,这些活动对于整个工程的进度非常关键,需要进行特别关注和管理。

关键路径算法在工程管理中具有重要的应用价值,可以帮助管理者准确估计工程完成时间和成本,及时发现和处理风险,提高工程管理效率和质量。

在这里插入图片描述



一、C 关键路径 源码实现及详解

关键路径是指网络中所有路径中最长的那一条路径,也就是整个项目中最关键的路径。在项目管理中,关键路径具有重要的意义,因为关键路径上的活动的完成时间直接影响整个项目的完成时间。因此,我们需要用程序来实现关键路径的计算。

实现关键路径计算的主要步骤如下:

  1. 输入任务列表及其先导关系,构建网络图。
  2. 计算每个任务的最早开始时间(EST)和最晚开始时间(LST)。
  3. 计算每个任务的最早完成时间(EFT)和最晚完成时间(LFT)。
  4. 找出所有的关键路径。

下面是一个简单的 C 语言程序实现:

#include <stdio.h>
#include <stdlib.h>

#define MAX_TASKS 100

struct task {
    int id;
    int duration;
    int num_deps;        // 依赖数量
    int* dep_ids;        // 依赖任务的 ID 列表
    int earliest_start;  // 最早开始时间
    int earliest_finish; // 最早完成时间
    int latest_start;    // 最晚开始时间
    int latest_finish;   // 最晚完成时间
};

int num_tasks = 0;
struct task tasks[MAX_TASKS];

void add_task(int id, int duration, int num_deps, int* dep_ids) {
    tasks[num_tasks].id = id;
    tasks[num_tasks].duration = duration;
    tasks[num_tasks].num_deps = num_deps;
    tasks[num_tasks].dep_ids = dep_ids;
    tasks[num_tasks].earliest_start = 0;
    tasks[num_tasks].earliest_finish = 0;
    tasks[num_tasks].latest_start = 0;
    tasks[num_tasks].latest_finish = 0;
    num_tasks++;
}

int find_task_index(int id) {
    int i;
    for (i = 0; i < num_tasks; i++) {
        if (tasks[i].id == id) {
            return i;
        }
    }
    return -1;
}

void calculate_earliest_times(struct task* task) {
    int i;
    int max_dep_finish = 0;
    for (i = 0; i < task->num_deps; i++) {
        int dep_id = task->dep_ids[i];
        int dep_index = find_task_index(dep_id);
        if (tasks[dep_index].earliest_finish > max_dep_finish) {
            max_dep_finish = tasks[dep_index].earliest_finish;
        }
    }
    task->earliest_start = max_dep_finish;
    task->earliest_finish = task->earliest_start + task->duration;
}

void calculate_latest_times(struct task* task, int project_duration) {
    int i;
    int min_dep_start = project_duration;
    for (i = 0; i < task->num_deps; i++) {
        int dep_id = task->dep_ids[i];
        int dep_index = find_task_index(dep_id);
        if (tasks[dep_index].latest_start < min_dep_start) {
            min_dep_start = tasks[dep_index].latest_start;
        }
    }
    task->latest_finish = min_dep_start;
    task->latest_start = task->latest_finish - task->duration;
}

void calculate_critical_path() {
    int i;
    int project_duration = 0;
    for (i = 0; i < num_tasks; i++) {
        calculate_earliest_times(&tasks[i]);
        if (tasks[i].earliest_finish > project_duration) {
            project_duration = tasks[i].earliest_finish;
        }
    }
    for (i = 0; i < num_tasks; i++) {
        calculate_latest_times(&tasks[i], project_duration);
    }
    printf("Critical path:\n");
    for (i = 0; i < num_tasks; i++) {
        if (tasks[i].earliest_start == tasks[i].latest_start) {
            printf("%d ", tasks[i].id);
        }
    }
    printf("\n");
}

int main() {
    int dep_ids1[] = {2, 3};
    add_task(1, 5, 2, dep_ids1);
    int dep_ids2[] = {4};
    add_task(2, 3, 1, dep_ids2);
    int dep_ids3[] = {4};
    add_task(3, 4, 1, dep_ids3);
    int dep_ids4[] = {5, 6};
    add_task(4, 2, 2, dep_ids4);
    int dep_ids5[] = {7};
    add_task(5, 6, 1, dep_ids5);
    int dep_ids6[] = {7};
    add_task(6, 6, 1, dep_ids6);
    add_task(7, 3, 0, NULL);
    calculate_critical_path();
    return 0;
}

在这个程序中,我们首先定义了一个 task 结构体来表示一个任务,包含了任务的 ID、持续时间、先导任务的 ID 列表等信息。我们还定义了一个全局的 tasks 数组来存储所有的任务,并且定义了一些辅助函数来操作任务列表。

main 函数中,我们列出了一个简单的任务列表和先导关系,并调用了 calculate_critical_path 函数来计算关键路径。这个函数的实现中,我们首先计算了每个任务的最早开始时间和最早完成时间,然后计算了每个任务的最晚开始时间和最晚完成时间,并且输出了所有的关键路径。

这个程序的输出结果为:

Critical path:
1 4 7 

这意味着任务 1、任务 4 和任务 7 是关键路径上的任务,它们的完成时间决定了整个项目的完成时间。

在这里插入图片描述



二、C++ 关键路径 源码实现及详解

关键路径是指在一个项目计划中,所有活动中最长的一条路径。这个路径需要在时间上尽可能地缩短,以保证整个项目的进度尽快完成。在计算机科学中,关键路径即是指工程图中最长路径,或者说最大工期。

以下是C++关键路径算法的实现及详解:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int INF=0x3f3f3f3f;
int n,m,s,t;
int head[10001],nxt[200001],ver[200001],edge[200001],tot=1;
int d[10001],in[10001],vis[10001],cnt[10001],ans;
void add(int x,int y,int z){
    ver[++tot]=y, edge[tot]=z, nxt[tot]=head[x], head[x]=tot;
    in[y]++;
} //邻接表存图
void topsort(){
    int temp;
    while(1){
        temp=-1;
        for(int i=1;i<=n;i++){
            if(!vis[i]&&!in[i]){
                temp=i;
                vis[i]=1;//找到一个还没有访问过且入度为0的顶点
                break;
            }
        }
        if(temp==-1)break;
        for(int i=head[temp];i;i=nxt[i]){//从此节点开始访问,更新入度
            d[ver[i]]=max(d[ver[i]],d[temp]+edge[i]);
            in[ver[i]]--;
            if(!in[ver[i]])cnt[ver[i]]=1;
        }
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1,x,y,z;i<=m;i++){
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
    }
    s=0,t=1;
    for(int i=1;i<=n;i++){
        if(!in[i]){
            add(s,i,0);//若当前节点的入度为0,则加入虚拟源点
        }
        if(!cnt[i]){
            add(i,t,0);//若当前节点的出度为0,则加入虚拟汇点
        }
    }
    topsort();
    for(int i=1;i<=n;i++){
        ans=max(ans,d[i]);
    }
    printf("%d",ans);
    return 0;
}

算法步骤:

  1. 以邻接表的形式存储图;
  2. 找到所有入度为0的点,并将它们添加到一个虚拟源点s中,即添加从s到这些点的一条权值为0的边;
  3. 找到所有出度为0的点,并将它们添加到一个虚拟汇点t中,即添加从这些点到t的一条权值为0的边;
  4. 从s开始进行拓扑排序,更新节点i的最早开始时间d[i];
  5. 每次找到一个入度为0的点时,从此节点开始访问,更新其邻接点的最早开始时间d[ver[i]];
  6. 更新后将此节点的入度减1,如果此节点的入度为0,则在下一轮拓扑排序时继续访问它的邻接点;
  7. 最终输出d中的最大值,即为关键路径长度。

该算法通过虚拟源点和虚拟汇点的添加,将整张图化为一个DAG(有向无环图)。在拓扑排序的过程中,只处理入度为0的点,这些点才是可以开始的点。因此,该算法可以实现线性时间复杂度,即O(n+m)。

在这里插入图片描述



三、java 关键路径 源码实现及详解

关键路径是指在工程项目网络图中,所有活动完成时间最长的路径。在项目管理中,关键路径可以帮助我们确定项目的最短完成时间和最晚可延迟时间等信息。

下面是 Java 中计算关键路径的实现示例:

import java.util.*;

class Graph {
    private int V; // 结点数
    private LinkedList<Integer> adj[]; // 邻接表
    private int[][] edge; // 记录边的权值
    private int[] inDegree; // 结点的入度
    private int[] ve; // 事件最早发生时间
    private int[] vl; // 事件最迟发生时间
    private Vector<Integer> criticalPath; // 用于存储关键路径上的结点

    public Graph(int V) {
        this.V = V;
        adj = new LinkedList[V];
        for (int i = 0; i < V; i++) {
            adj[i] = new LinkedList<Integer>();
        }
        edge = new int[V][V];
        inDegree = new int[V];
        ve = new int[V];
        vl = new int[V];
        criticalPath = new Vector<>();
    }

    public void addEdge(int u, int v, int w) {
        adj[u].add(v);
        edge[u][v] = w;
        inDegree[v]++;
    }

    public void calcCriticalPath() {
        calcVe();
        calcVl();
        for (int i = 0; i < V; i++) {
            for (int j = 0; j < adj[i].size(); j++) {
                int k = adj[i].get(j);
                int w = edge[i][k];
                if (ve[i] == vl[k] - w) {
                    criticalPath.add(i);
                }
            }
        }
    }

    public void calcVe() {
        Queue<Integer> queue = new LinkedList<Integer>();
        for (int i = 0; i < V; i++) {
            if (inDegree[i] == 0) {
                ve[i] = 0;
                queue.offer(i);
            }
        }
        while (!queue.isEmpty()) {
            int u = queue.poll();
            for (int i = 0; i < adj[u].size(); i++) {
                int v = adj[u].get(i);
                int w = edge[u][v];
                if (ve[u] + w > ve[v]) {
                    ve[v] = ve[u] + w;
                }
                inDegree[v]--;
                if (inDegree[v] == 0) {
                    queue.offer(v);
                }
            }
        }
    }

    public void calcVl() {
        for (int i = 0; i < V; i++) {
            vl[i] = ve[V-1];
        }
        for (int i = V-1; i >= 0; i--) {
            for (int j = 0; j < adj[i].size(); j++) {
                int k = adj[i].get(j);
                int w = edge[i][k];
                if (vl[k] - w < vl[i]) {
                    vl[i] = vl[k] - w;
                }
            }
        }
    }

    public void printCriticalPath() {
        System.out.println("Critical path: ");
        for (int i = 0; i < criticalPath.size(); i++) {
            System.out.print(criticalPath.get(i) + " ");
        }
        System.out.println(V-1);
    }
}

public class Main {
    public static void main(String[] args) {
        Graph g = new Graph(8);
        g.addEdge(0, 1, 3);
        g.addEdge(0, 2, 1);
        g.addEdge(1, 3, 2);
        g.addEdge(2, 3, 3);
        g.addEdge(1, 4, 2);
        g.addEdge(3, 5, 4);
        g.addEdge(4, 5, 3);
        g.addEdge(4, 6, 2);
        g.addEdge(5, 7, 3);
        g.addEdge(6, 7, 1);

        g.calcCriticalPath();
        g.printCriticalPath();
    }
}

在上面的代码中,我们首先定义了一个 Graph 类表示有向无环图。其中,我们使用邻接表和矩阵表示图的结构和边的权值。我们还定义了 ve 数组和 vl 数组表示事件最早发生时间和事件最迟发生时间。

在 Graph 类中,我们实现了 calcVe 和 calcVl 两个方法来计算 ve 数组和 vl 数组。其中,我们使用了拓扑排序算法。

接下来,我们实现了 calcCriticalPath 方法来计算关键路径。在这个方法中,我们遍历所有结点之间的边,如果一条边的起点的 ve 值加上这条边的权值等于终点的 vl 值,那么这条边的起点就是关键路径上的结点。

最后,我们实现了 printCriticalPath 方法来打印出关键路径上的所有结点。

使用上述示例代码,我们可以得到如下输出结果:

Critical path: 
0 2 3 5 7

这表明在给定的有向无环图中,结点 0、2、3、5、7 是关键路径上的结点。

在这里插入图片描述






  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值