图的应用-关键路径

714 阅读6分钟
  • AOE网概念

    在⼀一个表示⼯工程的带权有向图中,⽤用顶点表示事件,⽤用有向边表示活动,⽤用边上的权值表示活动 的持续时间,这种有向图的边表表示活动的⽹网,我们称之为AOE ⽹网(Activity On Edge Network)
    没有⼊入边的顶点称为始点或源点;
    没有出边的顶点称为终点或汇点;
    由于⼀一个⼯工程, 总有⼀一个开始,⼀一个结束.所以正常情况下,AOE⽹网只有⼀一个源点和⼀一个汇点.

  • AOE网关键名称解释

    路路径上各个活动所持续的时间之和称为路路径⻓长度
    从源点到汇点具有最⼤大的路路径叫关键路路径
    在关键路路径上的活动叫关键活动

  • 关键路径算法思路

    先定义几个求解关键路径过程中的几个核心参数

    1. 事件最早发生时间etv,即顶点的最早发生时间,也就是到达这个顶点的最早时间
    2. 事件最晚发生事件ltv,即顶点的最晚的发生事件,也就是到达这个顶点的最晚时间
    3. 活动的最早开工时间ete,即弧的最早开工时间
    4. 活动的最晚开工时间lte,即弧的最晚开工时间

    具体思路:首先拓扑排序计算出每个顶点的etv,然后在遍历所有顶点计算出ltv,最后再一次循环算出每个弧的ete和lte如果两者相同说明改弧是关键路径,

  • 求解事件最早发生时间etv

    按照拓扑排序的顺序求解出每个顶点的etv;

  • 求解事件最晚发生时间ltv

    这里可以先初始化ltv将每个顶点的ltv设置成汇点的etv,但是要按照拓扑排序的结果倒序求出:

  • 实现代码

    
    #include <stdio.h>
    #include "stdlib.h"
    
    #define OK 1
    #define ERROR 0
    #define TRUE 1
    #define FALSE 0
    #define MAXVEX 14 /* 最大顶点数,应由用户定义 */
    #define MAXEDGE 20
    #define INFINITYC 65535
    
    typedef int Status;    /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
    typedef char ElemType;
    
    /*邻接矩阵结构 */
    typedef struct
    {
        int vexs[MAXVEX];
        int arc[MAXVEX][MAXVEX];
        int numVertexes, numEdges;
    }MGraph;
    
    typedef struct EdgeNode{
        struct EdgeNode *next;
        int weight; // 权重
        int index; //索引
    }EdgeNode;
    
    typedef struct {
        int data;
        int in; // 入度
        EdgeNode * firstNode;
    }vertexNode,AdjList[MAXVEX];
    
    //图结构
    typedef struct
    {
        AdjList adjList;
        //图中当前顶点数和边数
        int numVertexes,numEdges;
    }graphAdjList,*GraphAdjList;
    
    /*1.构成AOV网图*/
    void CreateMGraph(MGraph *G)/* 构件图 */
    {
        int i, j;
        /* printf("请输入边数和顶点数:"); */
        G->numEdges=13;
        G->numVertexes=10;
    
        for (i = 0; i < G->numVertexes; i++)/* 初始化图 */
        {
            G->vexs[i]=i;
        }
    
        for (i = 0; i < G->numVertexes; i++)/* 初始化图 */
        {
            for ( j = 0; j < G->numVertexes; j++)
            {
                if (i==j)
                    G->arc[i][j]=0;
                else
                    G->arc[i][j]=INFINITYC;
            }
        }
    
        G->arc[0][1]=3;
        G->arc[0][2]=4;
        G->arc[1][3]=5;
        G->arc[1][4]=6;
        G->arc[2][3]=8;
        G->arc[2][5]=7;
        G->arc[3][4]=3;
        G->arc[4][6]=9;
        G->arc[4][7]=4;
        G->arc[5][7]=6;
        G->arc[6][9]=2;
        G->arc[7][8]=5;
        G->arc[8][9]=3;
        
    }
    
    /*2.将AOV网图借助邻近矩阵转换成邻接表结构*/
    void CreateALGraph(MGraph G,GraphAdjList *GL)
    {
        *GL = (GraphAdjList)malloc(sizeof(graphAdjList));
        (*GL)->numVertexes = G.numVertexes;
        (*GL)->numEdges = G.numEdges;
        //初始化(默认d入度都是0)
        for(int i = 0; i < G.numVertexes; i ++){
            (*GL)->adjList[i].data = G.vexs[i];
            (*GL)->adjList[i].in = 0;
            (*GL)->adjList[i].firstNode = NULL;
        }
        
        EdgeNode *node;
        for(int i = 0; i < G.numVertexes; i ++){
            for(int j = 0; j < G.numVertexes; j ++){
                if(G.arc[i][j] < INFINITYC && G.arc[i][j] > 0){
                    //存在指向关系
                    node = (EdgeNode *)malloc(sizeof(EdgeNode));
                    node->index = j;
                    node->weight = G.arc[i][j];
                    node->next = (*GL)->adjList[i].firstNode;
                    (*GL)->adjList[i].firstNode = node;
                    //对应入度要加一
                    (*GL)->adjList[j].in++;
                    
                }
            }
        }
    }
    
    /*3.拓扑排序. 若AOV网图无回路则输出拓扑排序的序列并且返回状态值1,若存在回路则返回状态值0*/
    /*拓扑排序:解决的是一个工程能否顺序进行的问题!*/
    /*
     思路:创建一个栈stack 将所有的入度为0的顶点入栈,然后进入下一个阶段先出栈,然后删除栈顶顶元素的所有弧信息,如果有
     入度为0 的顶点继续入栈,同时还要记录出栈元素的个数用于判断符合拓扑拍讯(出栈的个数=顶点的个数)
     **/
    
    //各个顶点的最早发生时间
    int *etv;
    //各个顶点的最晚发生时间
    int *ltv;
    //存储拓扑排序后的结果
    int *stack2;
    //栈顶元素的索引
    int top2;
    Status TopologicalSort(GraphAdjList GL){
        //初始化栈
        int stack[GL->numVertexes];
        int top = 0;
        
        //入度为0 的点入栈
        for(int i = 0; i < GL->numVertexes; i ++){
            if(GL->adjList[i].in == 0){
                stack[top ++] = i;
            }
        }
        
        if(top != 1){
            //说明源点没有或者存在多个(AOE网明确源点和汇点只会有一个)
            return ERROR;
        }
        
        //用于存放拓扑排序后的结果
        stack2 = (int *)malloc(sizeof(int) * GL->numVertexes);
        etv = (int *)malloc(sizeof(int) * GL->numVertexes);
        top2 = 0;
        //初始化etv(默认全部是0)
        for(int i = 0; i < GL->numVertexes; i ++){
            etv[i] = 0;
        }
        
        int count = 0;
        //开始出栈
        while (top > 0) {
            //删除栈顶元素对应的弧信息(也就是对应的顶点的入度减一)
            printf("%d -> ",stack[--top]);
            
            stack2[top2++] = stack[top];
            
            count ++;
            EdgeNode *node = GL->adjList[stack[top]].firstNode;
            int currentIndex = stack[top];
            while (node) {
                //求解各定点的etv
                int k = node->index;
                if(etv[currentIndex] + node->weight > etv[k]){
                    etv[k] = etv[currentIndex] + node->weight;
                }
                
                GL->adjList[node->index].in --;
                if(GL->adjList[node->index].in == 0){
                    //入栈
                    stack[top++] = node->index;
                }
                node = node->next;
            }
            
        }
        if(count == GL->numVertexes){
            return OK;
        }
        return ERROR;
    }
    
    //求关键路径, GL为有向网,则输出G的各项关键活动;
    void CriticalPath(GraphAdjList GL){
        for(int i = 0; i < GL->numVertexes; i ++){
            printf("%d ",etv[i]);
        }
        printf("\n\n");
        //先初始化ltv数组默认每个顶点的额最晚发生时间都是到达汇点的时间
        ltv = (int *)malloc(sizeof(int) * GL->numVertexes);
        for(int i = 0; i < GL->numVertexes; i ++){
            ltv[i] = etv[stack2[top2-1]];
        }
        //先求解ltv
        ltv[0] = 0;
        EdgeNode * node;
        while (top2-1 > 0) {
            node = GL->adjList[stack2[top2-1]].firstNode;
            while (node) {
                if(ltv[stack2[top2-1]] > ltv[node->index] - node->weight){
                    ltv[stack2[top2-1]] = ltv[node->index] - node->weight;
                }
                node = node->next;
            }
            top2 --;
        }
        
        //打印ltv
        for(int i = 0; i < GL->numVertexes; i ++){
            printf("%d ",ltv[i]);
        }
        
        //开始求解ete(最早开工时间)和lte(最晚开工时间) 如果ete==lte  则说明该路径是最短路径
        node = GL->adjList[0].firstNode;
        int ete,lte;
        printf("\n\n");
        printf("0");
        for(int i = 0; i < GL->numVertexes; i ++){
            node = GL->adjList[i].firstNode;
            //最早开工时间(其实就是上一个定点刚刚发生就立马开始,所以就等于上一个顶点的最早发生时间)
            ete = etv[i];
            while (node) {
                //最晚开工时间(当然就是在不延误工期的情况下最晚发生的时间,r当前定点最晚发生时间减去到达改定需要的权值就好)
                lte = ltv[node->index] - node->weight;
                if(ete == lte){
                    //说明是关键路径
                    printf("->%d",node->index);
                }
                node = node->next;
            }
        }
        
        
        
        
        
        
    }
    
    
    int main(int argc, const char * argv[]) {
        // insert code here...
        printf("Hello, World!\n");
        
        GraphAdjList GL;
        MGraph G;
        CreateMGraph(&G);
        CreateALGraph(G, &GL);
        TopologicalSort(GL);
        printf("\n\n");
        CriticalPath(GL);
        return 0;
    }