数据结构【第十三天】:图(四)

图的应用

三、拓扑排序

       在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系,这样的有向图为顶点表示活动的网,称为AOV网AOV网中不能存在回路

       设G=(V,E)是一个具有n个顶点的有向图,V中的顶点序列v1,v2,......,vn,满足若从顶点vi到vj有一条路径,则在顶点序列中顶点vi必在顶点vj之前,则我们称这样的顶点序列为一个拓扑序列。

       注意:拓扑序列并不是唯一的

拓扑排序即是对一个有向图构造拓扑序列的过程。构造时,若顶点全部输出则不存咋回路,若少顶点,则存在回路,即不构成AOV网

拓扑排序基本思想:

从AOV网中选择一个入度为0的顶点输出,然后删除此顶点和以此顶点为弧尾的弧,继续重复此步骤,直至全部输出顶点或者AOV网中不存在入度为0的顶点为止,在判断输出顶点数是否就是全部顶点数。

AOV网在邻接表的基础上给顶点表结点增加了一个入度域in,如下

邻接表顶点表结点结构
in data firstedge

对如下邻接表进行拓扑排序

各种结点的结构代码

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;

拓扑排序(利用栈)

//拓扑排序,若图GL无回路,则输出拓扑排序并返回ok,否则返回error
Status TopologicalSort(GraphAdjList GL)
{
    EdgeNode *e;
    int i,k,gettop;
    int top = 0;    //用于指向栈顶
    int count = 0;    //统计输出顶点个数 
    int *stack;    //建立栈,存储入度为0的顶点
    stack = (int *)malloc(GL->numVertexes * sizeof(int));
    for(i=0; i<GL->numVertexes; i++)    //遍历顶点
        if(GL->adjList[i].in == 0)
            stack[++top] = i;    //将入度为0的顶点入栈
    while(top!=0)    //栈不为空
    {
        gettop = stack[top--];    //栈顶的顶点下标出栈
        cout<<GL->adjList[gettop].data>>" ->";    //打印此顶点
        count++;    //统计输出顶点
        for(e=GL->adjList[gettop].firstedge;e;e=e->next)    
        //对该顶点的弧表进行遍历
        {
            k=e->adjvex;    //k为原顶点的邻接顶点
            if(!(--GL->adjList[k].in))     //该邻接顶点k的入度减一
                stack[++top] = k;    //若顶点k的入度为0,则入栈
        }
    }
    if(count<GL->numVertexes)    //输出顶点数小于全部顶点数
        return error;
    else
        return ok;
    
}

栈的初始化复杂度为O(n),while循环中进栈、出栈、入度减1的操作共执行了e次,所以整个算法的时间复杂度为O(n+e)

四、关键路径

在一个表示工程的带权有向图中,用顶点表示事件,用有向边表示活动,用边上的权值表示活动的持续时间,这种有向图的边表示活动的网,我们称为AOE网

AOE网中,没有入边的顶点称为始点或者源点,没有出边的顶点称为终点或者汇点,一般来说,AOE网只有一个源点和终点。

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

定义参数:
1.事件的最早发生时间etv:即顶点vk的最早发生时间

2.事件的最晚发生时间ltv:即顶点vk的最晚发生时间

3.活动的最早开工时间ete:即弧ak的最早发生时间

4.活动的最晚开工时间lte:即弧ak的最晚发生时间

可以根据1、2求3、4,然后判断ete[k]与lte[k]是不是相等就可以判断其是否为关键活动

例子:对如下图求关键路径

在求关键路径之前,需要进行拓扑排序,得到etv和拓扑序列列表

int *etv,*ltv;    //存储顶点发生的最早时间和最晚的时间
int *stack2;    //存储拓扑序列的栈

//拓扑排序,若图GL无回路,则输出拓扑排序并返回ok,否则返回error
Status TopologicalSort(GraphAdjList GL)
{
    EdgeNode *e;
    int i,k,gettop;
    int top = 0;    //用于指向栈顶
    int count = 0;    //统计输出顶点个数 
    int *stack;    //建立栈,存储入度为0的顶点
    stack = (int *)malloc(GL->numVertexes * sizeof(int));
    for(i=0; i<GL->numVertexes; i++)    //遍历顶点
        if(GL->adjList[i].in == 0)
            stack[++top] = i;    //将入度为0的顶点入栈

    top2 = 0;    //初始化栈2指针
     //事件最早发生数组创建和初始化
    etv = (int *)malloc(GL->numVertexes * sizeof(int));
    for(i=0; i<GL->numVertexes; i++)
        etv[i] = 0;
    //栈2初始化
    stack2 = (int *)malloc(GL->numVertexes * sizeof(int));

    while(top!=0)    //栈不为空
    {
        gettop = stack[top--];    //栈顶的顶点下标出栈
        stack2[++top] = gettop;    //将弹出顶点艳茹拓扑序列栈
        count++;    //统计输出顶点
        for(e=GL->adjList[gettop].firstedge;e;e=e->next)    
        //对该顶点的弧表进行遍历
        {
            k=e->adjvex;    //k为原顶点的邻接顶点
            if(!(--GL->adjList[k].in))     //该邻接顶点k的入度减一
                stack[++top] = k;    //若顶点k的入度为0,则入栈
            //比较各个邻接顶点发生的的最早事件,将各自最长的存入etv
            //类似于最短路径,不过这里求的是最长    
            if(etv[gettop] + e->weight > etv[k])
                etv[k] = etv[gettop] + e->weight
        }
    }
    if(count<GL->numVertexes)    //输出顶点数小于全部顶点数
        return error;
    else
        return ok;
    
}

上述代码可见,求etv[k]的最早发生公式为

关键路径的算法代码

//求关键路径,GL为有向网,输出GL的各项关键活动
void CriticalPath(GraphAdjList GL)
{
    EdgeNode *e;
    int i,gettop,k,j;
    int ete,lte;    //活动最早和最迟发生的时间变量
    TopologicalSort(GL);    //求拓扑排序序列,计算数组etv和stack2的值
    ltv = (int *)malloc(GL->numVertexes * sizeof(int));    //申请空间
    for(i=0;i<GL->numVertexes;i++)    //初始化ltv
    while(top2 != 0)
    {
        gettop = stack2[top--];    //拓扑序列出栈
        for(e=GL->adjList[gettop].firstedge;e;e=e->next)
        //依次遍历该顶点的所有邻接顶点    
        {
            k = e->adjvex;    //k为邻接顶点下标
            if(ltv[k]-e->weight<ltv[gettop])  //求各顶点发生的最晚时间
            {
                ltv[gettop] = ltv[k]-e->weight;
            }
        }
    }
    
    for(j=0;j<GL->numVertexes;j++)    //循环所有顶点
    {
        //循环所有顶点的邻接顶点
        for(e=GL->adjList[j].firstedge;e;e = e->next)
        {
            k = e ->adjvex;
            ete = etv[j];    //活动最早时间
            lte = ltv[k] - e->weight;    //活动最迟发生时间
            if(ete ==lte)    //相等的弧即在关键路径上
                printf("<v%d , v%d>length:%d,",
                GL->adjList[j].data,GL->adjList[k].data,e->weigeht);
        }
    }
}

上述代码中,在求ltv时可以发现,其实即是吧拓扑排序倒过来进行,计算公式如下

得到etv和ltv如下

上图可得的信息:

从v0到v9最大距离为27,而etv计算的即是每个点到v0的最大距离

而ltv = 用最大距离27 - 该顶点到v9的最大距离

(在代码计算中,先用ltv[k]-e->weight当做ltv[gettop]值,然后不断遍历其他的邻接点,更新找到最小的那个(即gettop-9距离最大))。

etv[1] = 3,ltv[1] = 7表示v1事件最早能在第三天开始,最迟也要在第7天开始

而ete表示弧<vk,vj>的最早开工时间,即ete = etv[k]

lte表示弧<vk,vj>的最晚开工时间,即lte = ltv[j]-len<vk,vj>

上述求关键路径的代码,时间复杂度为O(n+e);

猜你喜欢

转载自blog.csdn.net/KNOW_MORE/article/details/88595771