数据结构-图进阶-拓扑排序&&关键路径

323 阅读10分钟

AOV->拓扑序列->拓扑排序

AOV

在某个工程中,可以把顶点当做是一个活动事件,把顶点中的弧当做活动事件的优先顺序(优先级)。这就组成了一个有向网。在数据结构中我们通常叫为AOE网(Activity On Vertex Network)。

此处借用百度百科中一个列表,百度百科:拓扑排序

编号 课程名称 学前基础
C1 高等数学
C2 程序设计基础
C3 离散数学 C1,C2
C4 数据结构 C3,C5
C5 算法语言 C2
C6 编译技术 C4,C5
C7 操作系统 C4,C9
C8 普通物理 C1
C9 计算机原理 C8

拓扑序列&&拓扑排序

对一个有向无环图G进行拓扑排序,是将G进行线性化,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。

构造拓扑序列的过程会有两种结果

  1. 如果此⽹中的全部顶点被输出,则说明它不存在环(回路)的AOV⽹;
  2. 如果输出的顶点数少了,哪怕仅少了⼀个,也说明这个⽹存在环(回路),不是AOV⽹

拓扑排序算法解析

示例

用下面的例子进行算法解析

图的存储方式

邻接表的方式进行存储图

思路
  1. 用栈来存放入度为0的顶点
  2. 顶点出栈时将顶点的关联顶点的入度-1
  3. 再讲入度为0的顶点加入到栈中
  4. 循环上述操作
代码
#include <stdio.h>
#include "stdlib.h"

#define MAXVEX 14
#define MAXEDGE 20
#define OK 1
#define ERROR 0

typedef int Status;

//图 邻接矩阵
typedef struct {
    int vex[MAXVEX];
    int arc[MAXVEX][MAXVEX];
    int numVertexes, numEdges;
}MGragp;

//图 邻接表 边结构
typedef struct EdgeNode{
    int adjvex;
    int weight;
    struct EdgeNode *next;
}EdgeNode;

//图 邻接表 顶点结构
typedef struct vertexNode{
    int in;
    int data;
    EdgeNode *firstEdge;
}VertexNode, AdjList[MAXVEX];

//图 邻接表
typedef struct {
    AdjList adjList;
    int numVertexes, numEdges;
}GraphList, *GraphAdjList;

//创建图 邻接矩阵
void CreateMGraph(MGragp *g) {
    g->numVertexes = MAXVEX;
    g->numEdges = MAXEDGE;
    
    for (int i = 0; i < g->numVertexes; i++) {
        g->vex[i] = i;
        for (int j = 0; j < g->numVertexes; j++) {
            g->arc[i][j] = 0;
        }
    }
    
    g->arc[0][4]=1;
    g->arc[0][5]=1;
    g->arc[0][11]=1;
    g->arc[1][2]=1;
    g->arc[1][4]=1;
    g->arc[1][8]=1;
    g->arc[2][5]=1;
    g->arc[2][6]=1;
    g->arc[2][9]=1;
    g->arc[3][2]=1;
    g->arc[3][13]=1;
    g->arc[4][7]=1;
    g->arc[5][8]=1;
    g->arc[5][12]=1;
    g->arc[6][5]=1;
    g->arc[8][7]=1;
    g->arc[9][10]=1;
    g->arc[9][11]=1;
    g->arc[10][13]=1;
    g->arc[12][9]=1;
}

//转换成邻接表
void CreateGraphList(MGragp g, GraphAdjList *gl) {
    *gl = (GraphAdjList)malloc(sizeof(GraphList));
    (*gl)->numVertexes = g.numVertexes;
    (*gl)->numEdges = g.numEdges;
    
    for (int i = 0; i < g.numVertexes; i++) {
        (*gl)->adjList[i].in = 0;
        (*gl)->adjList[i].data = g.vex[i];
        (*gl)->adjList[i].firstEdge = NULL;
    }
    
    for (int i = 0; i < g.numVertexes; i++) {
        for (int j = 0; j < g.numVertexes; j++) {
            if (g.arc[i][j] == 1) {
                EdgeNode *edge = (EdgeNode*)malloc(sizeof(EdgeNode));
                edge->adjvex = j;
                edge->next = (*gl)->adjList[i].firstEdge;
                (*gl)->adjList[i].firstEdge = edge;
                (*gl)->adjList[j].in ++;
            }
        }
    }
}

//拓扑排序
Status TopologicalSort(GraphAdjList gl) {
    //栈 存放顶点下标
    int *stack = (int*)malloc(sizeof(int));
    int top = 0;
    //记录顶点个数
    int count = 0;
    
    //将入度为0的顶点入栈
    for (int i = 0; i < gl->numVertexes; i++) {
        if (gl->adjList[i].in == 0) {
            stack[++top] = i;
        }
    }
    printf("top = %d\n",top);
    
    //开始循环 条件栈不为空
    while (top != 0) {
        //出栈 && 打印
        int getTop = stack[top--];
        printf("%d -> ", gl->adjList[getTop].data);
        
        //记录
        count ++;
        
        //循环处理与当前顶点有联系的其他顶点的入度-1
        for (EdgeNode *edge = gl->adjList[getTop].firstEdge; edge; edge = edge->next) {
            //当入度为0 入栈
            if (--gl->adjList[edge->adjvex].in == 0) {
                stack[++top] = edge->adjvex;
            }
        }
    }
    printf("\n");
    
    //记录的顶点数量与总数量相等,证明没有回路
    if (count == gl->numVertexes) {
        return OK;
    } else {
        //有回路 报错
        return ERROR;
    }
}
运行
int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, 拓扑排序!\n");
    
    MGragp g;
    GraphAdjList gl;
    CreateMGraph(&g);
    CreateGraphList(g, &gl);
    int result = TopologicalSort(gl);
    printf("result: %d\n", result);
    
    return 0;
}

AOE->关键路径

首先图的关键路径,需要在拓扑排序的基础上进行,所以对拓扑排序理解的不透彻的,先把上面的知识点多巩固一段时间,再继续阅读下面的内容。

AOE

在⼀个表示⼯程的带权有向图中,⽤顶点表示事件,⽤有向边表示活动,⽤边上的权值表示活动的持续时间,这种有向图的边表表示活动的⽹,我们称之为AOE ⽹(Activity On Edge Network)

  • 没有入边的顶点称为始点或源点
  • 没有出边的顶点称为终点或汇点
  • AOE网只有一个源点和一个汇点

生活中的造车例子,权值单位是天:

图中外壳制造需要2天,发动机制造需要3天,造轮子一个轮子需要0.5天,...

等这些零部件造好后,几种起来需要0.5天,最后需要2天时间组装。

邻接表

其他相关名词解释

  • 事件最早发⽣的时间etv(earliest time of vertex):即顶点Vk的最早发⽣时间;
  • 事件最晚发⽣时间ltv(latest time of vertex):即顶点Vk的最晚发⽣时间,也就是每个顶点对应的事件最晚需要开始的时间,超出此时间将会延误整个⼯期;
  • 活动的最早开⼯时间ete(earliest time of edge):即弧Ak的最早发⽣时间;
  • 活动的最晚开⼯时间lte(latest time of edge):即弧Ak的最晚发⽣时间,也就 逻辑教育 是不推迟⼯期的最晚开⼯时间.

etv

etv:事件“最早”发生时间。用一个数组表示,元素是 当前顶点的开始时间,也就是当前结点“最晚”开始时间。这个地方理解起来会有点歧义。

  • 最早”:是所有事件的总时间当中的,当前事件发生的最早时间。
  • 最晚”:是当前事件(顶点),的所有前驱事件都满足的时间。代码中的实现是求Max值。

其实etv求得过程就是拓扑序列过程。另外etv数组下标不是事件(顶点)下标,所以还需要一个栈来表示顶点信息。 用栈的原因是为了求得ltv时逆序方便

初始化栈变量

int *etv;
int *stack2;
int top2;

拓扑排序部分代码

Status TopologicalSort(GraphAdjList GL) {
    ......
    //初始化栈
    top2 = 0;
    stack2 = (int*)malloc(sizeof(int) *GL->numVertexes);
    //初始化etv数组
    etv = (int*)malloc(sizeof(GL->numVertexes * sizeof(int)));
    for (int i = 0; i < GL->numVertexes; i++) {
        etv[i] = 0;
    }
    
    printf("TopologicSort:\t");
    while (top != 0) {
        ...... 
        for (EdgeNode *e = GL->adjList[gettop].firstedge; e; e = e->next) {
            ......
            //获取到开始事件的最大值
            if ((etv[gettop] + e->weight) > etv[k]) {
                etv[k] = etv[gettop] + e->weight;
            }
        }
    }
    ......
}

结果:

ltv

ltv:就是事件(顶点)最晚发生的时间,如果超出这个时间,就会延期工程。和etv一样,用一个数组表示,数组元素是最晚发生的时间。初始值为整个工程的时间,也就是栈顶元素V9的时间27

更新逻辑
  1. 获取栈顶元素,找到该顶点的所有后继顶点
  2. 在所有的后继顶点的对应的ltv中的值减去后继顶点中权值,在这些商中取最小的值当做当前顶点的对应的ltv的值

例如:下标8的顶点

顶点8的后继顶点只有一个9,权重是3。顶点9在ltv中的值是27,所以ltv[8] = min(ltv[8],ltv[9] - 3) = min(27, 27-3) = 24

在举一个多个后继顶点的例子,例如下标4的顶点

ltv[4] = min(ltv[4], ltv[7]-4, ltv[6]-9) = min(27, 19-4, 25-9) = 15

最终结果:

求ltv代码:

    //初始化
    ltv = (int*)malloc(sizeof(int) * GL->numVertexes);
    for (int i = 0; i < GL->numVertexes; i++) {
        ltv[i] = etv[GL->numVertexes - 1];
    }
    
    while (top2 != 0) {
        //取栈顶元素
        int gettop = stack2[top2--];
        
        for (EdgeNode *e = GL->adjList[gettop].firstedge; e; e = e->next) {
            int k = e->adjvex;
            //min操作
            if (ltv[k] - e->weight < ltv[gettop]) {
                ltv[gettop] = ltv[k] - e->weight;
            }
        }
    }

ete && lte

ete:最早开工时间,其实就是获取到etv中的元素

lte:最晚开工时间,也就是不推迟工期的最晚开工时间。

ete与lte相等,活动之间没有空闲时间,也就是最关键活动。

例如求得V0->V1和V0->V2哪个是关键活动,如图:

  • V0->V1:ete = etv[0] = 0, lte = ltv[1]-3 = 7-3 = 4,ete不等于lte,说明此路径不是关键路径
  • V0->V2:ete = etv[0] = 0, lte = ltv[2]-4 = 4-4 = 0,ete等于lte,说明是关键路径
    for (int i = 0; i < GL->numVertexes; i++) {
        for (EdgeNode *e = GL->adjList[i].firstedge; e; e = e->next) {
            int k = e->adjvex;
            int ete = etv[i];
            int lte = ltv[k] - e->weight;
            if (ete == lte) {
                printf("<%d-%d> length:%d\n", GL->adjList[i].data, GL->adjList[k].data, e->weight);
            }
        }
    }

关键路径的完整代码

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

#define OK 1
#define ERROR 0
#define MAXEDGE 30
#define MAXVEX 30
#define INFINITYC 65535

typedef int Status;

typedef struct {
    int vex[MAXVEX];
    int arc[MAXVEX][MAXVEX];
    int numVertexes, numEdges;
}MGraph;

typedef struct EdgeNode{
    int adjvex;
    int weight;
    struct EdgeNode *next;
}EdgeNode;

typedef struct VertexNode{
    int in;
    int data;
    EdgeNode *firstedge;
}VertexNode, AdjList[MAXVEX];

typedef struct {
    AdjList adjList;
    int numVertexes, numEdges;
}graphAdjList, *GraphAdjList;

void CreateMGraph(MGraph *G) {
    G->numEdges = 13;
    G->numVertexes = 10;
    
    for (int i = 0; i < G->numVertexes; i++) {
        G->vex[i] = i;
        for (int 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;
}

void CreateALGraph(MGraph G, GraphAdjList *GL) {
    *GL = (GraphAdjList)malloc(sizeof(graphAdjList));
    (*GL)->numVertexes = G.numVertexes;
    (*GL)->numEdges = G.numEdges;
    
    for (int i = 0; i < G.numVertexes; i++) {
        (*GL)->adjList[i].in = 0;
        (*GL)->adjList[i].data = G.vex[i];
        (*GL)->adjList[i].firstedge = NULL;
    }
    
    for (int i = 0; i < G.numVertexes; i++) {
        for (int j = 0; j < G.numVertexes; j++) {
            if (G.arc[i][j] != 0 && G.arc[i][j] < INFINITYC) {
                EdgeNode *e = (EdgeNode*)malloc(sizeof(EdgeNode));
                e->adjvex = j;
                e->weight = G.arc[i][j];
                e->next = (*GL)->adjList[i].firstedge;
                (*GL)->adjList[i].firstedge = e;
                (*GL)->adjList[j].in++;
            }
        }
    }
}

int *etv, *ltv;
int *stack2;
int top2;

Status TopologicalSort(GraphAdjList GL) {
    int *stack = (int *)malloc(sizeof(int) * GL->numVertexes);
    int count = 0;
    int top = 0;
    
    for (int i = 0; i < GL->numVertexes; i++) {
        if (0 == GL->adjList[i].in) {
            stack[++top] = i;
        }
    }
    
    //初始化栈
    top2 = 0;
    stack2 = (int*)malloc(sizeof(int) *GL->numVertexes);
    //初始化etv数组
    etv = (int*)malloc(sizeof(GL->numVertexes * sizeof(int)));
    for (int i = 0; i < GL->numVertexes; i++) {
        etv[i] = 0;
    }
    
    printf("TopologicSort:\t");
    while (top != 0) {
        int gettop = stack[top--];
        printf("%d -> ", GL->adjList[gettop].data);
        count ++;
        
        stack2[++top2] = gettop;
        
        for (EdgeNode *e = GL->adjList[gettop].firstedge; e; e = e->next) {
            int k = e->adjvex;
            if (--GL->adjList[k].in == 0) {
                stack[++top] = k;
            }
            //获取到开始事件的最大值
            if ((etv[gettop] + e->weight) > etv[k]) {
                etv[k] = etv[gettop] + e->weight;
            }
        }
    }
    printf("\n");
    
    if (count < GL->numVertexes) {
        return ERROR;
    } else {
        return OK;
    }
}

void CriticalPath(GraphAdjList GL) {
    TopologicalSort(GL);
    
    printf("etv:\n");
    for (int i = 0; i < GL->numVertexes; i++) {
        printf("etv[%d] = %d \n", i , etv[i]);
    }
    printf("\n");
    
    //初始化
    ltv = (int*)malloc(sizeof(int) * GL->numVertexes);
    for (int i = 0; i < GL->numVertexes; i++) {
        ltv[i] = etv[GL->numVertexes - 1];
    }
    
    while (top2 != 0) {
        //取栈顶元素
        int gettop = stack2[top2--];
        
        for (EdgeNode *e = GL->adjList[gettop].firstedge; e; e = e->next) {
            int k = e->adjvex;
            //min操作
            if (ltv[k] - e->weight < ltv[gettop]) {
                ltv[gettop] = ltv[k] - e->weight;
            }
        }
    }
    
    printf("ltv:\n");
    for (int i = 0; i < GL->numVertexes; i++) {
        printf("ltv[%d] = %d\n", i , ltv[i]);
    }
    printf("\n");
    
    for (int i = 0; i < GL->numVertexes; i++) {
        for (EdgeNode *e = GL->adjList[i].firstedge; e; e = e->next) {
            int k = e->adjvex;
            int ete = etv[i];
            int lte = ltv[k] - e->weight;
            if (ete == lte) {
                printf("<%d-%d> length:%d\n", GL->adjList[i].data, GL->adjList[k].data, e->weight);
            }
        }
    }
}

运行:

int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, 关键路径!\n");
    
    MGraph G;
    CreateMGraph(&G);
    GraphAdjList GL;
    CreateALGraph(G, &GL);
    
    CriticalPath(GL);
    
    
    return 0;
}