数据结构和算法(16)- 图的拓扑排序和关键路径

560 阅读5分钟

一.拓扑排序

1.定义

用顶点表示活动,用弧表示活动间的优先关系的有向图称为顶点表示活动的网,简称AOV-网

2.拓扑排序的过程:

  • 在有向图中选中一个无前驱的顶点并且将它输出。
  • 从图中删除该顶点和所有以它为尾的弧。
  • 重复前两步,直至不存在无前驱的顶点。
  • 若此时输出的顶点数小于有向图中的顶点数,则说明有向图中存在环,否则输出的顶点序列即为一个拓扑序列。

3.核心代码

Status TopologicalSort(GraphAdjList GL){
    EdgeNode *e;
    int i,k,gettop;
    //用于栈指针下标
    int top=0;
    //用于统计输出顶点的个数
    int count=0;
    //建栈将入度为0的顶点入栈(目的:为了避免每次查找时都要遍历顶点表查找有没有入度为0的顶点)
    int *stack=(int *)malloc(GL->numVertexes * sizeof(int) );
    //1.遍历邻接表-顶点表,将入度in为0的顶点入栈
    /*参考图1> 此时stack栈中应该成为0,1,3.即V0,V1,V3的顶点入度为0*/
    for(i = 0; i< GL->numVertexes; i++)
        //将入度为0的顶点入栈
        if(0 == GL->adjList[i].in)
            stack[++top] = i;
    printf("top = %d\n",top);
    
    //2.循环栈结构(当栈中有元素则循环继续)
    while(top!=0)
    {
        //出栈
        gettop = stack[top--];
        printf("%d -> ",GL->adjList[gettop].data);
        //输出顶点,并计数
        count++;
        //遍历与栈顶相连接的弧
        for(e = GL->adjList[gettop].firstedge; e; e = e->next)
        {
            //获取与gettop连接的顶点
            k=e->adjvex;
            //1.将与gettop连接的顶点入度减1;
            //2.判断如果当前减1后为0,则入栈
            if( !(--GL->adjList[k].in) )
                //将k入栈到stack中,并且top加1;
                stack[++top]=k;
        }
    }
    /*思考:3 -> 1 -> 2 -> 6 -> 0 -> 4 -> 5 -> 8 -> 7 -> 12 -> 9 -> 10 ->13 -> 11
     这并不是唯一的拓扑排序结果.
     分析算法:将入度为0的顶点入栈的时间复杂度为O(n), 而之后的while 循环,每个顶点进一次栈,并且出一次栈. 入度减1, 则共执行了e(边的总数)次. 那么整个算法的时间复杂度为O(n+e)*/
    printf("\n");
    //判断是否把所有的顶点都输出. 则表示找到了拓扑排序;
    if(count < GL->numVertexes)
        return ERROR;
    else
        return OK;
}

4.输出拓扑序列

3 -> 1 -> 2 -> 6 -> 0 -> 4 -> 5 -> 8 -> 7 -> 12 -> 9 -> 10 -> 13 -> 11

二.关键路径的求解

1.定义

AOE-网是一个带权的有向无环图,顶点表示时间,弧表示活动,权表示活动持续的时间。通常AOE-网用来表示估算工程的完成时间。

2.关键路径的求解过程

  • 对图中顶点进行排序,在排序过程中按拓扑序列求出每个事件的最早发生时间etv(i)
  • 按逆拓扑序列求出每个事件的最迟发生时间ltv(i)
  • 求出每个活动的最早开始时间ete
  • 求出每个活动的最晚开始时间lte
  • 找出ete(i) = lte(i)的活动,即为关键活动。
  • 由关键活动形成的由源点到汇点的每一条路径就是关键路径,关键路径有可能不止一条。

3.核心代码

//求关键路径, GL为有向网,则输出G的各项关键活动;
void CriticalPath(GraphAdjList GL){
    EdgeNode *e;
    int i,gettop,k,j;
    //声明活动最早发生时间和最迟发生时间变量;
    int ete,lte;
    //求得拓扑序列,计算etv数组以及stack2的值
    TopologicalSort(GL);
    //打印etv数组(事件最早发生时间)
    printf("etv:\n");
    for(i = 0; i < GL->numVertexes; i++)
        printf("etv[%d] = %d \n",i,etv[i]);
    printf("\n");
    //事件最晚发生时间数组
    ltv = (int *)malloc(sizeof(int) * GL->numVertexes);
    //初始化ltv数组
    for (i = 0; i < GL->numVertexes; i++) {
        //初始化ltv数组. 赋值etv最后一个事件的值
        ltv[i] = etv[GL->numVertexes-1];
        //printf("ltv[%d] = %d\n",i,ltv[i]);
    }
    //计算ltv(事件最晚发生时间) 出栈求ltv
    while (top2 != 0) {
        
        //出栈(栈顶元素)
        gettop = stack2[top2--];
        
        //找到与栈顶元素连接的顶点; 例如V0是与V1和V2连接
        for (e = GL->adjList[gettop].firstedge; e; e = e->next) {
            //获取与gettop 相连接的顶点
            k = e->adjvex;
            //计算min(ltv[k]-e->weight,ltv[gettop])
            if (ltv[k] - e->weight < ltv[gettop]) {
                //更新ltv 数组
                ltv[gettop] = ltv[k] - e->weight;
            }
        }
    }
    //打印ltv 数组
    printf("ltv:\n");
    for (i = 0 ; i < GL->numVertexes; i++) {
        printf("ltv[%d] = %d \n",i,ltv[i]);
    }
    printf("\n");
    //求解ete,lte 并且判断lte与ete 是否相等.相等则是关键活动;
    //2层循环(遍历顶点表,边表)
    for(j=0; j<GL->numVertexes;j++)
    {
        for (e = GL->adjList[j].firstedge; e; e = e->next) {
            //获取与j连接的顶点;
            k = e->adjvex;
            //ete 就是表示活动 <Vk, Vj> 的最早开工时间, 是针对这条弧来说的.而这条弧的弧尾顶点Vk 的事件发生了, 它才可以发生. 因此ete = etv[k];
            ete = etv[j];
            //lte 表示活动<Vk, Vj> 的最晚开工时间, 但此活动再晚也不能等Vj 事件发生才开始,而是必须在Vj 事件之前发生. 所以lte = ltv[j] - len<Vk, Vj>.
            lte = ltv[k]-e->weight;
            //如果ete == lte 则输出j,k以及权值;
            if (ete == lte) {
                printf("<%d-%d> length:%d\n",GL->adjList[j].data, GL->adjList[k].data, e->weight);
            }
        }
    }
}

4.输出

Hello, 关键路径的求解!
TopologicSort:	0 -> 1 -> 2 -> 3 -> 4 -> 6 -> 5 -> 7 -> 8 -> 9 -> 
etv:
etv[0] = 0 
etv[1] = 3 
etv[2] = 4 
etv[3] = 12 
etv[4] = 15 
etv[5] = 11 
etv[6] = 24 
etv[7] = 19 
etv[8] = 24 
etv[9] = 27 

ltv:
ltv[0] = 0 
ltv[1] = 7 
ltv[2] = 4 
ltv[3] = 12 
ltv[4] = 15 
ltv[5] = 13 
ltv[6] = 25 
ltv[7] = 19 
ltv[8] = 24 
ltv[9] = 27 

<0-2> length:4
<2-3> length:8
<3-4> length:3
<4-7> length:4
<7-8> length:5
<8-9> length:3