数据结构之图的拓扑排序和关键路径

拓扑排序

百度百科上对拓扑排序的解释:对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。

图的拓扑排序是解决工程能否顺序执行。

在图结构中,只有有向无环图才有拓扑排序,其中每次能排在线性序列上的都是图中入度为 0 的结点。如果开始有多个入度为 0 的结点,可以随便安排哪一个,无硬性要求。

先来看看如下有向无环图

在这里插入图片描述

按照每次选择出入度为0的结点的准则,进行排序

在这里插入图片描述

对于图中的结点输出,需要辅助的栈结构或者是队列结构,将入度为0的结点都存入栈或者队列中,以此作为条件来进行深度遍历图,直到栈或者队列为空。

代码

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

typedef char VertexType;
#define MAXVEX  100          //最大顶点数        Maximum vertex

typedef struct EdgeNode         //边表结点
{
    int adjvex;                 //邻接顶点域
    int weight;                 //权值
    struct EdgeNode *next;
}EdgeNode;

typedef struct VertexNode       //顶点表结点
{
    int in;                     //顶点入度
    VertexType data;            //存储顶点信息
    EdgeNode *firstedge;        //边表的头指针
}VertexNode, AdjList[MAXVEX];

typedef struct
{
    AdjList adjlist;
    int numVertexes;
    int numEdges;
}GraphAdjList;

void create_Graph(GraphAdjList *G)
{
    int i, j;
    EdgeNode *e;
    printf("Please enter the number of vertexes and edges : ");
    scanf("%d %d", &G->numVertexes, &G->numEdges);

    for (i = 0; i < G->numVertexes; i++)
    {
        getchar();
        scanf("%d", &G->adjlist[i].in);
        getchar();
        scanf("%c", &G->adjlist[i].data);
       // scanf("%d %c", &G->adjlist[i].in, &G->adjlist[i].data);
        G->adjlist[i].firstedge = NULL;
    }

    for (int k = 0; k < G->numEdges; k++)
    {
        printf("Please enter vertex serial number on the edge like (vi, vj)  :");
        scanf("%d %d", &i, &j);
        e = (EdgeNode *)malloc(sizeof(EdgeNode));
        e->adjvex = j;
        e->next = G->adjlist[i].firstedge;
        G->adjlist[i].firstedge = e;
    }
}

bool TopologicalSort(GraphAdjList G)
{
    EdgeNode *e;
    int k, gettop;
    int top = -1;       //栈的标记
    int Count = 0;      //统计输出顶点的个数
    int *Stack;         //声明栈
    Stack = (int *)malloc(sizeof(int) * G.numVertexes);     //创建栈
    for (int i = 0; i < G.numVertexes; i++)
    {
        if (G.adjlist[i].in == 0)
        {
            //找到入度为0的顶点存入栈中
            Stack[++top] = i;
        }
    }

    while(top != -1)
    {
        gettop = Stack[top--];      //出栈


        Count++;

        if (Count >= G.numVertexes)
        {
            printf("%c", G.adjlist[gettop].data);
        }
        else
        {
            printf("%c -> ", G.adjlist[gettop].data);
        }

        for (e = G.adjlist[gettop].firstedge; e; e = e->next)
        {
            k = e->adjvex;
            if (!(--G.adjlist[k].in))
            {
                Stack[++top] = k;
            }
        }
    }
    printf("\n");

    if (Count < G.numVertexes)
    {   //不满足顶点数则存在环
        return false;
    }
    else
    {
        return true;
    }
}

int main()
{
    GraphAdjList G;

    create_Graph(&G);

    //拓扑排序算法
    if (!TopologicalSort(G))
    {
        printf("该AOV网存在环!\n");
    }
    else
    {
        printf("该AOV网不存在环!\n");
    }

    return 0;
}

关键路径

先来看看下面的图

在这里插入图片描述

由上面的AOE网图分析出,组装一辆车时需要将很多部件组装在一起,若将这些一步一步进行那就是工厂要倒闭了,可以同时进行,并且这些不同部件耗费的时间长短不一,若要给验收人一个等待验收的时间,那就是组装这些部件中用时最长的作为等待验收的时间(选择时间长一点的),这就是所谓的关键时间了;对于带权的有向无环网图来说,关键路径就麻烦点。给出书中的AOE网图

在这里插入图片描述

图中顶点表示事件,弧表示活动。

事件的最早发生时间,比如 v2 的最早发生时间(也就是从 V2 开始)是 4

事件的最晚发生时间,v2 的最晚发生时间是在 V5 或者 V3 的时间上减去与 V2 构成的权值,其实这是关键路径的特殊之处(早和晚的都相等)。

那么活动的最早开工时间和事件的最早开工事件是一样的;活动的最晚开工时间也会和事件的发生时间相似;只是活动的最早和最晚是用来判断关键路径的。

该AOE网图的关键路径如下

在这里插入图片描述

代码

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

typedef char VertexType;
#define MAXVEX 100

int *etv, *ltv;     //事件最早发生时间和最晚发生时间
int *Stack2;        //用于存储拓扑排序列的栈
int top2;

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

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

typedef struct
{
    AdjList adjList;
    int numVertexes;
    int numEdges;
}GraphAdjList;

void creat_Graph(GraphAdjList *G)
{
    int i, j, numWeight;
    EdgeNode *e;
    printf("Please enter the number of vertexes and edges : ");
    scanf("%d %d", &G->numVertexes, &G->numEdges);

    for (i = 0; i < G->numVertexes; i++)
    {
        getchar();
        scanf("%d", &G->adjList[i].in);
        getchar();
        scanf("%c", &G->adjList[i].data);
       // scanf("%d %c", &G->adjlist[i].in, &G->adjlist[i].data);
        G->adjList[i].firstedge = NULL;
    }

    for (int k = 0; k < G->numEdges; k++)
    {
        printf("Please enter vertex serial number on the edge like (vi, vj) and the weight :");
        scanf("%d %d %d", &i, &j, &numWeight);
        e = (EdgeNode *)malloc(sizeof(EdgeNode));
        e->adjvex = j;
        e->weight = numWeight;
        e->next = G->adjList[i].firstedge;
        G->adjList[i].firstedge = e;
    }
}

void TopologicalSort(GraphAdjList G)
{
    EdgeNode *e;
    int k, gettop;
    int top = -1;
    int Count = 0;      //统计输出顶点的个数
    int *Stack;
    Stack = (int *)malloc(sizeof(int) * G.numVertexes);

    for (int i = 0; i < G.numVertexes; i++)
    {
        if (0 == G.adjList[i].in)
        {
            Stack[++top] = i;
        }
    }

    top2 = -1;
    //事件最早发生时间
    etv = (int *)malloc(sizeof(int) * G.numVertexes);
    for(int i = 0; i < G.numVertexes; i++)
    {
        //初始化
        etv[i] = 0;
    }
    Stack2 = (int *)malloc(sizeof(int) * G.numVertexes);

    while(top != -1)
    {
        gettop = Stack[top--];
        Count++;
        Stack2[++top2] = gettop;

        for (e = G.adjList[gettop].firstedge; e; e = e->next)
        {
            k = e->adjvex;
            if (!(--G.adjList[k].in))
            {
                Stack[++top] = k;
            }
            //求各顶点事件最早发生时间值
            if ((etv[gettop] + e->weight) > etv[k])
            {
                etv[k] = etv[gettop] + e->weight;
            }
        }
    }

}

void CriticalPath(GraphAdjList G)
{
    EdgeNode *e;
    int gettop, k;
    TopologicalSort(G);

    //事件最晚发生的时间
    ltv = (int *)malloc(sizeof(int) * G.numVertexes);

    for (int i = 0; i < G.numVertexes; i++)
    {
        ltv[i] = etv[G.numVertexes - 1];
    }

    while (top2 != -1)
    {
        gettop = Stack2[top2--];
        for (e = G.adjList[gettop].firstedge; e; e = e->next)
        {
            //求各顶点事件的最迟发生时间ltv值
            k = e->adjvex;
            if (ltv[k] - e->weight < ltv[gettop])
            {
                //求各顶点事件最晚发生时间
                ltv[gettop] = ltv[k] - e->weight;
            }
        }
    }

    int ete, lte;   //活动最早发生时间和最晚发生时间
    for (int j = 0; j < G.numVertexes; j++)
    {
        for (e = G.adjList[j].firstedge; e; e = e->next)
        {
            k = e->adjvex;
            //活动最早发生时间
            ete = etv[j];
            //活动最晚发生时间
            lte = ltv[k] - e->weight;
            if (ete == lte)
            {
                printf("<%c, %c> length: %d ,", G.adjList[j].data, G.adjList[k].data, e->weight);
            }

        }
    }
}

int main()
{
    GraphAdjList G;

    creat_Graph(&G);

    CriticalPath(G);

    return 0;
}
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值