数据结构拓扑排序以及关键路径(出度邻接表)C语言 完整代码

这篇具有很好参考价值的文章主要介绍了数据结构拓扑排序以及关键路径(出度邻接表)C语言 完整代码。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一.问题描述

现实生活中一项工程通常会拆分成多个部分来进行,这些部分有些相互之间有发生的前提关系,还有些可以同时发生且不会互相打扰,但是合理且充分的利用时间来完成项目是一个问题。在项目完成的过程中,那些项目的完成时间被压缩可以压缩工程的总时间,以便于提高整个工程的完成效率,而且过程中所有项目不可以产生回环。如何合理的安排项目和找到关键项目是我们所要研究的问题。

二.算法设计

1.关键路径的算法设计

通过问题分析,发现解决问题用图来进行逻辑存储并且使用拓扑排序判断是否有环来寻找关键路径,将项目中的每个事件赋值于图的每个顶点,活动我们定义为图中每个顶点之间的关系并且带有权值以便记忆活动的信息。以此产生一个AOE-网来估算工程的完成时间。

由于整个工程只有一个开始点和一个完成点,故在正常的情况(无环)下,网中只有个入度为零的点叫做(称为源点)和一个出度为零的点(称为汇点)。由于AOV-网中有些活动可以并行的进行,所以完成工程的最短时间是从开始点到完成点的最长路径的长度。路径长度最长的路径叫做关键路径。

我们可以假设开始点事v1,从v1到vi的最长路径长度叫做事件vi的最早发生时间。这个时间决定了所有以vi为尾的弧所表示的活动的最早开始时间。我们用e(i)表示活动ai的最早开始时间。还可以定义一个活动的最迟开始时间l(i),这个是在不推迟整个工程完成的前提下,活动ai最迟必须开始进行的时间。两者之差l(i)—e(i)意味着完成活动ai的时间余量。我们把l(i)==e(i)的活动叫做关键活动。显然,关键路径上的所有活动都是关键活动,因此提前完成非关键活动并不能加快工程的进度。

由上分析可知,辨别关键活动就是要找e(i)=l(i)的活动。为了求得A0E﹣网中活动的 e(i)和l(i),首先应求得事件的最早发生时间ve(j)和最迟发生时间vl(j)。如果活动Ai,由弧< j , k >表示,其持续时间记为 dut(<j,k>)则有如下关系

e (i)=ve ( j )

l(i)=vl(k)-dut(<j,k>)

求ve (j)和vl(j)需分两步进行:

(1)从 ve (0)=0开始向前递推

ve(j)=Max{ve(i)+dut(<i,j>)}

<i,j>∈T, j=1,2,...,n-1

其中,T是所有以第 j 个顶点为头的弧的集合。

(2)从 vl (n-1)= ve (n-1)起向后递推

vl(i)=Min{vl(j)-dut(<i,j>)}

<i,j>∈S, i=n-2,...,0

其中,S 是所有以第 i 个顶点为尾的弧的集合。

这两个递推公式的计算必须分别在拓扑有序和逆拓扑有序的前提下进行。也就是说,ve(j-1)必须在Vj的所有前驱的最早发生时间求得之后才能确定,而 vl( j-1)则必须在Vj的所有后继的最迟发生时间求得之后才能确定。因此,可以在拓扑排序的基础上计算 ve (j-1)和 vl (j-1)。

由此得到如下的所述求关键路径的算法:

(1)输入e条弧<j,k>,建立AOE-网的存储结构;

(2)从源点v0出发,令ve[0]=0,按拓扑有序求其余各顶点的最早发生时间ve[i](1<i<n-1)。如果得到的拓扑有序序列中顶点个数小于网中顶点数n,则说明网中存在环,不可以求关键路径,算法终止;否则执行步骤(3)。

(3)从汇点vn出发,令vl[n-1]=ve[n-1],按逆序拓扑排序求其余各顶点的最迟发生时间vl(n-2>i>2);

(4)根据各顶点的ve和vl值,求每条弧s的最早开始时间e(s)和最迟开始时间l(s)。若某条弧满足条件e(s)=l(s),则为关键活动。

2.拓扑排序算法设计

基于上面的AOV-网,我们只需重复下面的两步,直至全部顶点均已输出,或者当前图中不存在无前驱的顶点为止。后一种情况则说明有向图中存在环。

(1)在有向图中选一个没有前驱的顶点并输出之

(2)从图中删除该顶点和所有以它为尾的弧。

三.代码的具体实现

1.首先是邻接表的数据结构类型定义。


typedef struct ArcNode {//表结点的定义
    int adjvex;  //该弧所指向的顶点的位置
    struct ArcNode* nextarc;  //指向下一条弧的指针
    int info;//弧的相关信息
}ArcNode;
typedef struct VNode {  //顶点结点的定义
    VertexType data;  //顶点信息
    ArcNode* firstarc;  //指向第一条依附于该顶点的弧的指针
    int hang;
    int rudushu;
}VNode, AdjList[MAX_VERTEX_NUM];//AdjLst表示邻接表的类型
typedef struct {
    AdjList vertices;    //顶点数组
    int vexnum, arcnum;  //图当前的顶点数和弧数
    int kind;            //图的种类标志
}ALGraph;//图的结构定义

图的存储结构采用邻接表,在邻接表中,对图中每个顶点建立一个单链表,第i个单链表中的结点表示依附于顶点Vi的边(对有向图是以顶点Vi为尾的弧)。每个结点由3个域组成,其中邻接点域(adjvex)指示与顶点Vi邻接的点在图中的位置,链域(nextarc)指示下一条边或弧的结点;数据域(info)存储和边或弧相关的信息,如权值等。每个链表上附设一个表头结点。在表头结点中,除了设有链域(firstarc)指向链表中第一个结点之外,还设有存储顶点Vi的名或其他有关信息的数据域(data)。

图的存储结构使用的是出度领接表,为了方便,在结构体定义的时候用一个rudushu来代表此顶点的如度数,其中的hang其实就是每个顶点的下标。

表结点:

adjvex

nextarc

info

头结点:

data

firstarc

  1. 接下来就是主函数,其实就是看个执行顺序就行了。


int main()
{
    ALGraph G;
    Inject(&G);
    TopologicalSort(G);
    CriticalPath(G);
    return 0;
}

先是定义了一个图G,Inject是对图进行初始化,TopologicalSort是拓扑排序,CriticalPath是关键路径。

  1. 先看图的初始化。


int LocateVex(ALGraph G, VertexType a)
{
    for (int i = 0; i < G.vexnum; i++)
    {
        if (a == G.vertices[i].data)
        {
            return G.vertices[i].hang;
        }
    }
    return -1;
}

void Inject(ALGraph* G)
{
    //G = (ALGraph)malloc(MAX_VERTEX_NUM * sizeof(ALGraph));
    printf("请输入图的顶点数:");
    scanf_s("%d", &G->vexnum);
    printf("请输入图的边数:");
    scanf_s("%d", &G->arcnum);
    for (int i = 0; i < G->vexnum; i++)
    {
        printf("%d ", i);
        G->vertices[i].hang = i;//将顶点数组每行的信息存进去,方便以后用
        getchar();
        scanf_s("%c", &G->vertices[i].data);//对每个顶点赋值
        //getchar();//清空缓存区*************我的理解就是吃回车
        G->vertices[i].firstarc = NULL;//顶点结点的指针暂时指向空
    }
    VertexType vi, vj;
    int i, j, s;//vi->vj  s是权值的暂时存储
    for (int k = 0; k < G->arcnum; k++)
    {
        printf("按照vi->vj的顺序以及权值:");
        getchar();
        scanf_s("%c", &vi);
        getchar();
        scanf_s("%c", &vj);
        scanf_s("%d", &s);
        i = LocateVex(*G, vi);
        j = LocateVex(*G, vj);//找位置
        ArcNode* p = (ArcNode*)malloc(sizeof(ArcNode));
        p->adjvex = j;//头插法
        p->info = s;
        p->nextarc = G->vertices[i].firstarc;
        G->vertices[i].firstarc = p;
    }
}

以上就是图的初始化,需要先输入图的顶点数以及边数,来大致确定图的结构。

然后使用邻接表的储存结构来存储,第一个for循环来确定顶点表的具体信息来对顶点表初始化。简单来说下一步肯定就是把每个顶点的边的信息用单链表的方式链在顶点表各个顶点的后面。(领接表的结构我写后面了)

第二个循环就是来写入图中每条边的信息,在链接的时候用i和j分别代表指向和被指向,也就是vi->vj的顺序,通过LocateVex来遍历整个顶点表,将j的顶点链接在i的顶点表所在行的后面。(采用头插法)

4.接下来是拓扑排序的具体实现与代码。


int TopologicalSort(ALGraph G)//拓扑排序
{
    int sum = 0;//sum是成功删除掉的顶点数
    int i, k;//
    ArcNode* p;
    for (i = 0; i < G.vexnum; i++)
        G.vertices[i].rudushu = FindInDeree(G, G.vertices[i].hang);//找出入度为0的
    SqStack S;//S1是拓扑排序的逆序存储,S2是正序存储,修火车的方法
    InitStack(&S1);
    InitStack(&S2);
    InitStack(&S);//栈的初始化
    ve = (int*)malloc(G.vexnum * sizeof(int));
    for (int n = 0; n < G.vexnum; n++)
    {
        ve[n] = 0;
    }
    for (int i = 0; i < G.vexnum; i++)//建零入度顶点S
    {
        if (G.vertices[i].rudushu == 0)//入度为0者进栈
            Push(&S, i);//入度为0者进栈
    }
    while (StackEmpty(S))
    {
        i = Pop(&S);
        printf("%c->", G.vertices[i].data);//输出i号顶点并计数
        ++sum;
        Push(&S1, i);
        for (p = G.vertices[i].firstarc; p; p = p->nextarc)
        {
            k = p->adjvex;
            G.vertices[k].rudushu--;//对i号顶点的每个邻接点的入度都减1
            if (!G.vertices[k].rudushu)
                Push(&S, k);
            if ((ve[i] + p->info) > ve[k])//在拓扑排序中就实现ve的修改,取最大值
                ve[k] = ve[i] + p->info;
        }
    }
    if (sum < G.vexnum)
    {
        printf("有回路");
        return 0;
    }
    else
    {
        printf("无回路\n");

        return 1;
    }
}

为了避免重复检测入度为零的顶点,可设置一个栈来暂存所有入度为零的顶点。其中S1和S2是全局变量,S1是拓扑排序的正序存储,S2是拓扑排序的逆序存储。

在栈的使用过程中可以利用栈的先进后出的特性,逆序输出拓扑排序,这样在对于从终点到起点进行分析的最晚开始事件的计算有着异曲同工之妙。

5.关键路径的寻找。


void CriticalPath(ALGraph G)
{
    ArcNode* p;         
    vl = (int*)malloc(G.vexnum * sizeof(int));
    for (int i = 0; i < G.vexnum; i++)//对ltv初始化
        vl[i] = ve[G.vexnum - 1];
    while (StackEmpty(S1))//对vl的修改,取每条边的所得数值的最小值
    {
        int i = Pop(&S1);//利用逆序拓扑排序
        for (p = G.vertices[i].firstarc; p; p = p->nextarc)
        {
            int k = p->adjvex;
            if ((vl[k] - p->info) < vl[i])
                vl[i] = vl[k] - p->info;
        }
    }
    int e, l;
    for (int n = 0; n < G.vexnum; n++)//遍历整个顶点表
    {
        for (p = G.vertices[n].firstarc; p; p = p->nextarc)//遍历每个顶点的链表
        {
            int k = p->adjvex;
            e = ve[n];
            l = vl[k] - p->info;
            if (e == l)
            {
                printf("(%c->%c)", G.vertices[n].data, G.vertices[k].data);
            }
        }
    }
}//关键路径

最晚事件开始时间刚好利用拓扑排序的逆序进行计算。

四.程序测试
  1. 测试用例

拓扑排序邻接表,数据结构,算法,图论,c语言,Powered by 金山文档
  1. 测试结果文章来源地址https://www.toymoban.com/news/detail-758005.html

拓扑排序邻接表,数据结构,算法,图论,c语言,Powered by 金山文档
五.完整代码

#include<stdio.h>
#include<stdlib.h>
#define MAX_VERTEX_NUM 20
#define _NO_CRT_STDIO_INLINE
#define _CRT_INTERNAL_SCANF_SECURECRT
typedef int SElemType;
typedef char  VertexType;//顶点信息数据类型的定义
typedef struct ArcNode {//表结点的定义
    int adjvex;  //该弧所指向的顶点的位置
    struct ArcNode* nextarc;  //指向下一条弧的指针
    int info;//弧的相关信息
}ArcNode;
typedef struct VNode {  //顶点结点的定义
    VertexType data;  //顶点信息
    ArcNode* firstarc;  //指向第一条依附于该顶点的弧的指针
    int hang;
    int rudushu;
}VNode, AdjList[MAX_VERTEX_NUM];//AdjLst表示邻接表的类型
typedef struct {
    AdjList vertices;    //顶点数组
    int vexnum, arcnum;  //图当前的顶点数和弧数
    int kind;            //图的种类标志
}ALGraph;//图的结构定义
typedef struct {
    SElemType* base;  //在栈构造之前和销毁之后,base的值为NULL
    SElemType* top;   //栈顶指针
    int stacksize;    //当前已经分配的存储空间,以元素为单位
}SqStack;
int* ve, * vl;//事件的最早时间和最晚时间    全局变量
SqStack S1;
SqStack S2;


int LocateVex(ALGraph G, VertexType a)
{
    for (int i = 0; i < G.vexnum; i++)
    {
        if (a == G.vertices[i].data)
        {
            return G.vertices[i].hang;
        }
    }
    return -1;
}

void Inject(ALGraph* G)
{
    //G = (ALGraph)malloc(MAX_VERTEX_NUM * sizeof(ALGraph));
    printf("请输入图的顶点数:");
    scanf_s("%d", &G->vexnum);
    printf("请输入图的边数:");
    scanf_s("%d", &G->arcnum);
    for (int i = 0; i < G->vexnum; i++)
    {
        printf("%d ", i);
        G->vertices[i].hang = i;//将顶点数组每行的信息存进去,方便以后用
        getchar();
        scanf_s("%c", &G->vertices[i].data);//对每个顶点赋值
        //getchar();//清空缓存区*************
        G->vertices[i].firstarc = NULL;//顶点结点的指针暂时指向空
    }
    VertexType vi, vj;
    int i, j, s;//vi->vj  s是权值的暂时存储
    for (int k = 0; k < G->arcnum; k++)
    {
        printf("按照vi->vj的顺序以及权值:");
        getchar();
        scanf_s("%c", &vi);
        getchar();
        scanf_s("%c", &vj);
        scanf_s("%d", &s);
        i = LocateVex(*G, vi);
        j = LocateVex(*G, vj);//找位置
        ArcNode* p = (ArcNode*)malloc(sizeof(ArcNode));
        p->adjvex = j;//头插法
        p->info = s;
        p->nextarc = G->vertices[i].firstarc;
        G->vertices[i].firstarc = p;
    }
}

int FindInDeree(ALGraph G, int k)
{
    int sum = 0;    //入度数
    ArcNode* p;   //p指针用来扫描每个顶点所发出的边
    for (int i = 0; i < G.vexnum; i++)//正确循环遍历邻接表每个结点
    {
        p = G.vertices[i].firstarc;
        while (p != NULL)
        {
            if (p->adjvex == k)
            {
                sum++;           //统计顶点的度
                break;
            }
            p = p->nextarc;      //循环遍历结点的所有弧
        }
    }
    return sum;          //返回入度
}

int InitStack(SqStack* S)  //创造一个空栈
{
    S->base = (SElemType*)malloc(MAX_VERTEX_NUM * sizeof(SElemType));
    if (!S->base)exit(1);   //判空
    S->top = S->base;
    S->stacksize = MAX_VERTEX_NUM;
    return 1;
}

void Push(SqStack* S, int k)
{
    *S->top++ = k;
}
int StackEmpty(SqStack S)
{
    if (S.top == S.base)
    {
        return 0;
    }
    return 1;
}

int Pop(SqStack* S)
{
    if (S->top == S->base) return 0;
    int i;
    i = *--S->top;
    return i;
}

int TopologicalSort(ALGraph G)//拓扑排序
{
    int sum = 0;//sum是成功删除掉的顶点数
    int i, k;//
    ArcNode* p;
    for (i = 0; i < G.vexnum; i++)
        G.vertices[i].rudushu = FindInDeree(G, G.vertices[i].hang);//找出入度为0的
    SqStack S;//S1是拓扑排序的逆序存储,S2是正序存储,修火车的方法
    InitStack(&S1);
    InitStack(&S2);
    InitStack(&S);//栈的初始化
    ve = (int*)malloc(G.vexnum * sizeof(int));
    for (int n = 0; n < G.vexnum; n++)
    {
        ve[n] = 0;
    }
    for (int i = 0; i < G.vexnum; i++)//建零入度顶点S
    {
        if (G.vertices[i].rudushu == 0)//入度为0者进栈
            Push(&S, i);//入度为0者进栈
    }
    while (StackEmpty(S))
    {
        i = Pop(&S);
        printf("%c->", G.vertices[i].data);//输出i号顶点并计数
        ++sum;
        Push(&S1, i);
        for (p = G.vertices[i].firstarc; p; p = p->nextarc)
        {
            k = p->adjvex;
            G.vertices[k].rudushu--;//对i号顶点的每个邻接点的入度都减1
            if (!G.vertices[k].rudushu)
                Push(&S, k);
            if ((ve[i] + p->info) > ve[k])//在拓扑排序中就实现ve的修改,取最大值
                ve[k] = ve[i] + p->info;
        }
    }
    if (sum < G.vexnum)
    {
        printf("有回路");
        return 0;
    }
    else
    {
        printf("无回路\n");

        return 1;
    }
}

void CriticalPath(ALGraph G)
{
    ArcNode* p;         
    vl = (int*)malloc(G.vexnum * sizeof(int));
    for (int i = 0; i < G.vexnum; i++)//对ltv初始化
        vl[i] = ve[G.vexnum - 1];
    while (StackEmpty(S1))//对vl的修改,取每条边的所得数值的最小值
    {
        int i = Pop(&S1);//利用逆序拓扑排序
        for (p = G.vertices[i].firstarc; p; p = p->nextarc)
        {
            int k = p->adjvex;
            if ((vl[k] - p->info) < vl[i])
                vl[i] = vl[k] - p->info;
        }
    }
    int e, l;
    for (int n = 0; n < G.vexnum; n++)//遍历整个顶点表
    {
        for (p = G.vertices[n].firstarc; p; p = p->nextarc)//遍历每个顶点的链表
        {
            int k = p->adjvex;
            e = ve[n];
            l = vl[k] - p->info;
            if (e == l)
            {
                printf("(%c->%c)", G.vertices[n].data, G.vertices[k].data);
            }
        }
    }
}//关键路径



int main()
{
    ALGraph G;
    Inject(&G);
    TopologicalSort(G);
    CriticalPath(G);
    return 0;
}

到了这里,关于数据结构拓扑排序以及关键路径(出度邻接表)C语言 完整代码的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 超详细讲解实现拓扑排序、关键路径

    今天,小编带着大家来学习图中非常重要的一环,拓扑排序和关键路径! 目录 一. 绪论——实际应用 二. 拓扑排序 (一).含义 (二).实现原理  (三).代码实现 三. 关键路径 (一).含义 (二).实现原理  (三).代码实现 首先,我们需要知道的是,拓扑排序是关键路径

    2023年04月14日
    浏览(32)
  • 【数据结构与算法】图的遍历与拓扑排序

    再上一篇中我们讲了树的两种存储方式:【数据结构与算法】图——邻接表与邻接矩阵 这一篇我们可以用数组来模拟邻接表。 假设现在我们要进行n次操作,实现无向图。 首先需要一个 保存是哪个节点 的数组 e 然后就是类似指针数组的 表 h ,每个表都会连一串单链表 e,ne

    2024年02月04日
    浏览(34)
  • 教学计划编制问题(数据结构 有向图 拓扑排序)

     本文对以下教学计划编制问题的解决作出实现,主要使用c语言(带一点cpp),开发环境为codeblocks 17.12,希望对各位读者有所帮助。(源码和数据文件可在主页获取,同时还有使用视频文件压缩包,数据文件需要和exe在同一目录下,结合某读者的意见同时放到github了 ) 地址如下

    2024年02月09日
    浏览(38)
  • python算法与数据结构(搜索算法和拓扑排序算法)---深度优先搜索

    了解树/图的 深度遍历,宽度遍历 基本原理; 会使用python语言编写 深度遍历,广度遍历 代码; 掌握 拓扑排序算法 搜索引擎 提到搜索两个子,大家都应该会想到搜索引擎,搜索引擎的基本工作步骤; 网页爬取 — 数据预处理 — 排序 — 查询 第一步,网页爬取,非常重要,

    2024年01月20日
    浏览(40)
  • 【数据结构——有向图】有环无环判定、拓扑排序(DFS、BFS)

    有向图(Directed Graph),也被称为有向图形或方向图,是一种图的类型。在有向图中,图中的边具有方向,从一个顶点指向另一个顶点。 在有向图中,每个顶点表示一个实体,而有向边则表示实体之间的关系或连接。这种有方向性的边表明了连接的起点和终点之间的单向关系

    2024年02月09日
    浏览(39)
  • 数据结构——关键路径

    ——本节内容为Bilibili王道考研《数据结构》P67视频内容笔记。 目录 一、基本概念 1.AOE网 2.AOE网的性质  3.关键路径 4.最早最晚时间 二、求关键路径 1.步骤 2.举例 三、关键活动/路径特性 1.AOE网         在带权有向图中,以顶点表示事件,以有向边表示活动,以边上的权值表

    2024年02月07日
    浏览(34)
  • 数据结构--6.2关键路径

    AOE网:         在一个表示工程的带权有向图中,用顶点表示事件,用有向边上的权值表示活动表示持续时间,这种有向图的边表示活动的网,我们称为AOE网(Activity On Edge Network)。 我们把AOE网中没有入边的顶点称为始点或源点,没有出边的顶点称为终点或汇点。   ——

    2024年02月09日
    浏览(27)
  • 【数据结构课程设计】关键路径问题

    1 问题描述与功能需求分析 1.1问题描述 1) 任务:设计一个程序求出完成整项工程至少需要多少时间以及整项工程中的关键活动。 2)基本要求: (1)对一个描述工程的 AOE 网,应判断其是否能够顺利进行。 (2)若该工程能顺利进行,输出完成整项工程至少需要多少时间,以及

    2024年02月10日
    浏览(33)
  • 【数据结构】有向无环图(AOE-网)的关键路径(C语言)

    把用顶点表示事件,用弧表示活动,弧的权值表示活动所需要的时间的有向无环图称为 边表示活动的网 ,简称 AOE-网 。 在AOE-网中存在唯一的、入度为0的顶点,称为 源点 ;存在唯一的、出度为0的顶点,称为 汇点 。从源点到汇点的最长路径长度即为完成整个工程任务所需的

    2024年02月07日
    浏览(32)
  • 数据结构中链表的实现以及排序

    本期和大家主要分享的是关于数据结构中双向链表的实现过程,那么话不多说,来具体看看吧! 来分析一下,这里呢定义了一个int的数据类型,表明整个链表存放的是整形的数据;其次定义了链表节点的数据类型,其中包括了此节点存放的数据以及链接向下一个节点的地址;

    2024年02月02日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包