美文网首页
图的应用(拓扑排序和关键路径问题)

图的应用(拓扑排序和关键路径问题)

作者: 85ca4232089b | 来源:发表于2020-05-21 12:13 被阅读0次

拓扑排序简介

设G = (V,E)是一个具有n个顶点的有向图, V中的顶点序列列V1,V2,.....,Vn.若满⾜足从顶点Vi 到Vj有一条路路径,则在顶点序列列Vi 必须在Vj 之前, 则我们称这样的顶点序列列成为拓扑序列
所谓拓拓扑排序,其实就是对⼀一个有向图构造拓拓扑序列列的过程. 构造过程拓拓扑序列列会产⽣生2个结果:

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


AOV 图的存储问题

邻接表结点结构

执行顺序

代码实现逻辑

结构设计



算法基本思路:
从AOV⽹网中选择⼀个入度为0的顶点输出,然后从删去此顶点,并删除以 此顶点为尾的弧. 继续重复此步骤,直到输出全部顶点或AOV⽹网中不不存在入度为0的顶点 为⽌.
代码算法分析:
在这个算法实现过程,我们需要借助一个数据结构栈.来帮助我们解决避免每次查找时, 都要去遍历AOV图中的顶点表查找有没有入度为0的顶点.

  1. 创建一个栈(stack),⽤用来存储⼊度in为0 的顶点序号;
  2. 遍历AOV图中顶点表,判断⼊入度为0的顶点全部入栈;
#include "stdio.h"
#include "stdlib.h"

#include "math.h"
#include "time.h"

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXEDGE 20
#define MAXVEX 14
#define INFINITYC 65535

/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int Status;

/*邻接矩阵结构 */
typedef struct
{
    int vexs[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;


/*1.构成AOV网图*/
void CreateMGraph(MGraph *G)/* 构件图 */
{
    int i, j;
    
    /* printf("请输入边数和顶点数:"); */
    G->numEdges=MAXEDGE;
    G->numVertexes=MAXVEX;
    
    /* 初始化图 */
    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++)
        {
            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;
    
}

/*2.将AOV网图借助邻近矩阵转换成邻接表结构*/
void CreateALGraph(MGraph G,GraphAdjList *GL)
{
    int i,j;
    EdgeNode *e;
    
    //创建图
    *GL = (GraphAdjList)malloc(sizeof(graphAdjList));
    //对图中的顶点数.弧数赋值
    (*GL)->numVertexes=G.numVertexes;
    (*GL)->numEdges=G.numEdges;
    
    //读入顶点信息,建立顶点表
    for(i= 0;i <G.numVertexes;i++)
    {
        (*GL)->adjList[i].in=0;
        (*GL)->adjList[i].data=G.vexs[i];
        //将边表置为空表
        (*GL)->adjList[i].firstedge=NULL;
    }
    
    //建立边表
    for(i=0;i<G.numVertexes;i++)
    {
        for(j=0;j<G.numVertexes;j++)
        {
            if (G.arc[i][j]==1)
            {
                //创建空的边表结点
                e=(EdgeNode *)malloc(sizeof(EdgeNode));
                //邻接序号为j
                e->adjvex=j;
                // 将当前顶点上的指向的结点指针赋值给e
                e->next=(*GL)->adjList[i].firstedge;
                //将当前顶点的指针指向e
                (*GL)->adjList[i].firstedge=e;
                (*GL)->adjList[j].in++;
                
            }
        }
    }
}

/*3.拓扑排序. 若AOV网图无回路则输出拓扑排序的序列并且返回状态值1,若存在回路则返回状态值0*/
/*拓扑排序:解决的是一个工程能否顺序进行的问题!*/
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;
}
int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, 拓扑排序!\n");
    MGraph G;
    GraphAdjList GL;
    int result;
    CreateMGraph(&G);
    CreateALGraph(G,&GL);
    result=TopologicalSort(GL);
    printf("result:%d",result);
    return 0;
}

关键路径

关键路径是指设计中从输入到输出经过的延时最长的逻辑路径。优化关键路径是一种提高设计工作速度的有效方法。一般地,从输入到输出的延时取决于信号所经过的延时最大路径,而与其他延时小的路径无关。在优化设计过程中关键路径法可以反复使用,直到不可能减少关键路径延时为止。EDA工具中综合器及设计分析器通常都提供关键路径的信息以便设计者改进设计,提高速度。

思考:
例如,造⼀辆汽⻋车.我们需要先造各种各样的
零件,部件. 最终才能完成汽⻋车的组装.
如右图,这些零部件基本都是在流⽔水线同时 生产的.假如造⼀个轮⼦子,需要0.5天的时间, 造⼀个发动机需要3天时间. 造⼀个车底盘 需要2天时间,其他零部件需要3天时间,全部 零部件集中到一处需要0.5天.组装成汽⻋车需 要2天时间.请问一个汽车厂造一台汽⻋车,最 短需要多长时间?

AOE

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


• 路路径上各个活动所持续的时间之和称为路路径⻓长度
• 从源点到汇点具有最⼤大的路路径叫关键路路径
• 在关键路路径上的活动叫关键活动
• 事件最早发生的时间etc (earliest time of edge); 即弧Ak 的最早发⽣生时间;
• 事件最晚发生时间ltv (latest time of vertex): 即顶点Vk 的最晚发⽣生时间,也就
是每个顶点对应的事件最晚需要开始的时间,超出此时间将会延误整个⼯工期;
• 活动的最早开工时间 ete (latest time of edge); 即弧Ak 的最早发⽣生时间
• 活动的最晚开⼯时间etv (earliest time of vertex):即弧Ak 的最晚发⽣生时间,也就
是不不推迟⼯工期的最晚开⼯工时间

算法思路路

求事件的最早发⽣生时间etv 的过程,就是从头到尾去找拓拓扑序列列的过 程. 所以在求解关键路路径之前,我们需要调⽤用⼀一次拓拓扑排序的序列列去计 算etv 和拓拓扑序列列列列表.
思考:

  1. 原来的拓拓扑排序算法需要修改吗?
  2. 如何借助拓拓扑排序求解etv(事件最早发⽣生时间)?
#include "stdio.h"
#include "stdlib.h"

#include "math.h"
#include "time.h"

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0

#define MAXEDGE 30
#define MAXVEX 30
#define INFINITYC 65535

typedef int Status;    /* Status是函数的类型,其值是函数结果状态代码,如OK等 */

/* 邻接矩阵结构 */
typedef struct
{
    int vexs[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;

/* **************************** */

/* 关于AOE网图的存储代码段-Begin */
//1.完成AOE网图关于邻接矩阵的存储
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.将邻近矩阵转化成邻接表
void CreateALGraph(MGraph G,GraphAdjList *GL){

    int i,j;
    EdgeNode *e;

    *GL = (GraphAdjList)malloc(sizeof(graphAdjList));

    (*GL)->numVertexes=G.numVertexes;
    (*GL)->numEdges=G.numEdges;

    //读入顶点信息,建立顶点表
    for(i= 0;i <G.numVertexes;i++)
    {
        (*GL)->adjList[i].in=0;
        (*GL)->adjList[i].data=G.vexs[i];
        //将边表置为空表
        (*GL)->adjList[i].firstedge=NULL;
    }

    //建立边表
    for(i=0;i<G.numVertexes;i++)
    {
        for(j=0;j<G.numVertexes;j++)
        {
            if (G.arc[i][j]!=0 && G.arc[i][j]<INFINITYC)
            {
                e=(EdgeNode *)malloc(sizeof(EdgeNode));
                //邻接序号为j
                e->adjvex=j;
                e->weight=G.arc[i][j];
                //将当前顶点上的指向的结点指针赋值给e
                e->next=(*GL)->adjList[i].firstedge;
                //将当前顶点的指针指向e
                (*GL)->adjList[i].firstedge=e;
                (*GL)->adjList[j].in++;

            }
        }
    }
}
/* 关于AOE网图的存储代码段-End! */
int *etv,*ltv; /* 事件最早发生时间和最迟发生时间数组,全局变量 */
int *stack2;   /* 用于存储拓扑序列的栈 */
int top2;       /* 用于stack2的指针*/

//拓扑排序
Status TopologicalSort(GraphAdjList GL){

    //若GL无回路,则输出拓扑排序序列且返回状态OK, 否则返回状态ERROR;
    EdgeNode *e;
    int i,k,gettop;
    //栈指针下标;
    int top = 0;
    //用于统计输出的顶点个数.作为拓扑排序是否存在回路的判断依据;
    int count = 0;
    //建栈,将入度in = 0的顶点入栈;
    int *stack = (int *)malloc(GL->numVertexes * sizeof(int));

    //遍历顶点表上入度in �= 0 入栈
    for (i = 0; i < GL->numVertexes;i++) {
        //printf("%d %d\n",i,GL->adjList[i].in);
        if ( 0 == GL->adjList[i].in ) {
            stack[++top] = i;
        }
    }

    //* stack2 的栈指针下标
    top2 = 0;
    //* 初始化拓扑序列栈
    stack2 = (int *)malloc(sizeof(int) * GL->numVertexes);
    //* 事件最早发生时间数组
    etv = (int *)malloc(sizeof(GL->numVertexes * sizeof(int)));
    //* 初始化etv 数组
    for (i = 0 ; i < GL->numVertexes; i++) {
        //初始化
        etv[i] = 0;
    }

    printf("TopologicSort:\t");
    while (top != 0) {
        gettop = stack[top--];
        printf("%d -> ", GL->adjList[gettop].data);
        count++;

        //将弹出的顶点序号压入拓扑排序的栈中;
        stack2[++top2] = gettop;
        
        //例如gettop为V0 ,那么与V0相连接的结点就有etv[1] = 3; etv[2] = 4;
        //例如gettop为V1 ,那么与V1连接的结点就有etv[4]= 3+6=9; etv[3] = 8;
        //例如gettop为V2 ,那么与V2连接的结点就有etv[5]= 4+7=11; etv[3] = 12;
        //例如gettop为V3 ,那么与V3连接的结点就有etv[4]= 12+3=15;
        for(e = GL->adjList[gettop].firstedge; e; e = e->next)
        {
            k = e->adjvex;
            
            //将i顶点连接的邻接顶点入度减1,如果入度减一后为0,则入栈
            if(!(--GL->adjList[k].in))
                stack[++top] = k;

            //求各顶点事件的最早发生的时间etv值
            //printf("etv[gettop]+e->weight = %d\n",etv[gettop]+e->weight);
            //printf("etv[%d] = %d\n",k,etv[k]);
            if ((etv[gettop] + e->weight) > etv[k]) {
                etv[k] = etv[gettop] + e->weight;
            }
        }

    }
    printf("\n");
    
    //打印etv(事件最早发生时间数组)
//    for (i = 0; i < GL->numVertexes; i++) {
//        printf("etv[%d] = %d\n",i,etv[i]);
//    }
//    printf("\n");
    
    if(count < GL->numVertexes)
        return ERROR;
    else
        return OK;
    return OK;
}

//求关键路径, 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);
            }
        }
    }
    
}

int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, 关键路径的求解!\n");
    MGraph G;
    GraphAdjList GL;
    CreateMGraph(&G);
    CreateALGraph(G,&GL);
    
    //拓扑排序
    //TopologicalSort(GL);
    
    //关键路径
    CriticalPath(GL);

    return 0;
}

相关文章

  • 拓扑排序和关键路径求值

    拓扑排序和关键路径的求值都是对图的应用,严格来说其实是对有向图应用。 我们先来描述一下拓扑排序,拓扑排序就是根据路...

  • 图的应用(拓扑排序和关键路径问题)

    拓扑排序简介 设G = (V,E)是一个具有n个顶点的有向图, V中的顶点序列列V1,V2,.....,Vn.若满...

  • 图的最短路径和拓扑排序

    拓扑排序 2、图的最短路径 3、图的拓扑排序

  • 数据结构_图(1_图的概述)

    主要知识点 图的概述 图的存储结构 图的遍历 最小生成树 最短路径 拓扑排序 关键路径 一、图的概述 1.1 图的...

  • 数据结构基础学习之(图)

    主要知识点 图的概述 图的存储结构 图的遍历 最小生成树 最短路径 拓扑排序 关键路径 一、图的概念 图的定义: ...

  • Graph-一般算法

    和图相关的算法有:最小生成子树,最短路径,拓扑排序。 这里仅介绍最小生成树和最短路径,拓扑排序暂时省略。 最小生成...

  • 图的应用-拓扑排序与关键路径求解

    拓扑排序 AOV网   在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系,这样有向图为顶点表示...

  • Leetcode --- 课程表问题(拓扑排序)

    写在前:拓扑排序本质是BFS和贪心算法,是这两种算法在有向图应用的专有名词,即拓扑排序针对有向图问题。参考这里[h...

  • 拓扑排序+关键路径

    拓扑排序 有向无环图DAG顶点表示活动的网络AOV网:用DAG图表示一个工程,其顶点表示活动,有向边...

  • 数据结构-图及相关算法

    目录 基本概念及问题图的三种表示方式现实应用图的遍历最短路径 - Dijkstra算法拓扑排序最小生成树 基本概念...

网友评论

      本文标题:图的应用(拓扑排序和关键路径问题)

      本文链接:https://www.haomeiwen.com/subject/wdlinhtx.html