数据结构(21)图的关键路径

本文探讨了图的关键路径问题,关键路径是指完成工程所需的最短时间路径,与活动的持续时间有关。通过举例解释了关键路径的概念,并介绍了如何通过算法计算关键路径。文章最后提供了实现关键路径算法的代码文件。
摘要由CSDN通过智能技术生成

前言

图的关键路径同样是有向无环图的应用。在拓扑排序中,我们关心的问题是工程能否顺利进行的问题,与顶点的次序有关;而在关键路径中,我们关心的是完成工程所必须的最短时间的问题,同边的权值有关。

img_1

如图所示,其中顶点表示工程,边表示活动,边的权值表示活动的持续时间。

以要完成子工程E为例,需要先完成工程B、C,以及B、C的前提工程A。其中,A-B-E边的总权值为7,这意味着E的前提工程A-B-E要7个单位时间才能完成。而A-C-E边的总权值为5,这意味着E的另一个前提工程A-C-E要5个单位时间才能完成。

假设A工程从0时开始,那么E工程最早要在7时才能开始,并且B工程必须在6时就开始。而A-C只需要5个单位时间,即使它在5时能完成,也必须要等到7时才能开始E工程。也就是说,C工程不一定必须在4时就开始,它可以在5时开始(6时完成),也可以在6时开始(7时完成)。

可以发现,A-B工程的完成时间是没有弹性的,它的完成时间决定了E工程的开始时间;而A-C工程则不一定。

这时候,假设我们想提升工程进度,让E工程早一些开始,缩短A-C的时间是没有意义的,必须要从A-B工程入手。

因此可以说,A-B边是一条关键路径。

在图中,有一些活动可以并行进行,所以完成工程的最短时间是从开始到结束的最长路径长度(即路径上各个活动的持续时间),这条路径就叫关键路径。

也就是说,这条最长路径长度决定了整个工程的完成时间,分析关键路径的目的是区分出关键活动,以便提高关键活动的功效,缩短整个工程的完成时间。

关键路径算法的实现

计算图的关键路径,与图中每个工程的最早开始和最晚开始时间有关。

img_2

如图所示,我们从A开始,得到A的邻接顶点B。B的最晚开始时间为6,减掉A-B边的权值6,正好是A的最早开始时间0,说明A-B是一条关键路径,将其输出。

此时得到A的下一个邻接顶点C,C的最晚开始时间为6,减掉A-C边的权值4,得2,与A的最早开始时间不同,说明这不是一条关键路径,继续去找下一个邻接顶点。

这样遍历,就能得到整个图的关键路径。

因此,实现这个算法主要有三个步骤:

  1. 计算出每个工程最早开始的时间

  2. 计算出每个工程最晚开始的时间

  3. 若本结点的最早开始时间同【其邻接顶点的最晚开始时间减去边的权值】一致,则说明该边是关键路径。

同时还需要两个辅助空间,保存每个工程的最早开始时间和最晚开始时间。

//获取两个顶点之间边的权值
int GetWeight(GraphMtx *g,int v1,int v2){
   
    return (v1 == -1 || v2 == -1) ? 0 : g->Edge[v1][v2];
}
//关键路径
void CriticalPath(GraphMtx *g){
   
    int n = g->NumVertices;
    int *ve = (int *)malloc(sizeof(int)*n);
    int *vl = (int *)malloc(sizeof(int)*n);
    assert(ve != NULL && vl != NULL);
    //初始化
    for (int i = 0; i < n; i ++) {
   
        //保存最早开始时间
        ve[i] = 0;
        //保存最晚开始时间
        vl[i] = MAX_COST;
    }
    
    
    int j,w;
    //求解最早开始时间
    for (int i = 0; i < n; i ++) {
   
        //获得第一个邻接顶点
        j = GetFirstNeighbor(g, g->VerticesList[i]);
        while (j != -1) {
   
            //邻接顶点存在->获取边的值
            w = GetWeight(g, i, j);
            if ((ve[i] + w) > ve[j]) {
   
                //本顶点加上这条边的值后,比原来的时间长
                //说明工程要更晚才能开始->替换最早开始时间
                ve[j] = ve[i] + w;
            }
            
            //获取下一个邻接顶点
            j = GetNextNeighbor(g, g->VerticesList[i], g->VerticesList[j]);
        }
    }
    
    //求解最晚开始时间
    //最后一个不变
    vl[n-1] = ve[n-1];
    for (int i = n-2; i > 0; i --) {
   
        j = GetFirstNeighbor(g, g->VerticesList[i]);
        while (j != -1) {
   
            //邻接顶点存在-获取边的值
            w = GetWeight(g, i, j);
            if ((vl[j] - w) < vl[i]) {
   
                //本顶点减去这条边的值后,比原来的时间更短
                //说明这项工程要更早开始->替换最晚开始时间
                vl[i] = vl[j] - w;
            }
            j = GetNextNeighbor(g, g->VerticesList[i], g->VerticesList[j]);
        }
    }
    
    int Ae,Al;
    //求关键路径
    for (int i = 0; i < n; i ++) {
   
        j = GetFirstNeighbor(g, g->VerticesList[i]);
        while (j != -1) {
   
            Ae = ve[i];
            Al = vl[j] - GetWeight(g, i, j);
            if (Ae == Al) {
   
                printf("<%c,%c>是关键路径\n",g->VerticesList[i],g->VerticesList[j]);
            }
            j = GetNextNeighbor(g, g->VerticesList[i], g->VerticesList[j]);
        }
    }
}

全部代码

GraphMtx.h
#ifndef GraphMtx_h
#define GraphMtx_h

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值