图解数据结构之图、邻阶矩阵、邻接表、拓扑排序、AOE网及其关键路径

图(Graph)

参考视频

1、概念

(1)图的定义
  • 图是由两个集合组成V、E,记为G=(V,E)。
    • V是顶点(数据元素)的有穷非空集合
    • E是顶点偶对的有穷集合,这些点偶对称为边
  • 无向图:每条边都是没有方向的图,没有箭头
  • 有向图:如上,反之。每条边都是有方向的图
    • 顶点个数=表头结点个数
    • 边数=2倍表结点数

image-20240328200232643

(2)基本术语
  • 子图:

image-20240328200400942

  • 简单图: 如上图G1、G2都是

    • 不存在重复边
    • 不存在顶点的自身的边
  • 多重图:

    • 若图G中某两个结点之间的边数多于一条,又允许顶点通过同一条边和自己关联,则G为多重图。多重图的定义和简单图是相对的
  • 完全图(也称简单完全图)
    image-20240328201058622

  • 稀疏图和稠密图

    • 边或弧的个数e < n logn的图为稀疏图,否则稠密图。(n为图的数量)
  • 权和网

    • 图中的边可标上具有某种含义的数值,该数值称为边上的权
    • AOV网是有向完全图
  • 有权图与无权图

    如果图中的边有各自的权重,得到的图是有权图。比如地铁路线图,连接两站的边的权重可以是距离,也可以是价格,或者其他。反之,如果图的边没有权重,或者权重都一样(即没有区分),称为无权图

  • 连通图

    如果图中任意两点都是连通的,那么图被称作连通图。图的连通性是图的基本性质。无向图中的一个极大连通子图称为其的一个连通分量。

  • 图的存储

    常用的存储方式有两种**:邻接矩阵和邻接表。**

(3)知识图解

image-20240412221646399

(4)图的遍历

遍历结果通常不唯一

  • 深度优先:一条路走到黑,这条路不通了可以折返找没走过的路
  • 广度优先:一行一行的遍历、或者一列一列的遍历,地毯式搜索

image-20240430093226052

2、邻阶矩阵

  • 无向图
    • 特点:
      1.无向图的邻接矩阵是对称的,且主对角线元素全为0(因为自己到自己没有边)。
      2.顶点i的度=第i行(列)中1的个数。
      3.完全图的邻接矩阵中,主对角元素为0,其余全为1,如果带权重的话没有连接写无穷。按0101这样写就行

image-20240330201249553

  • 有向图

    特点:
    有向图的邻接矩阵可能不是对称的。
    顶点的出度=第i行元素之和;
    顶点的入度=第i列元素之和;
    顶点的=第i行元素之和+第i列元素之和。

image-20240330201254099

3、邻接表

  • 无向图

    • 邻接表不唯一
    • 有连接的都要指向

    image-20240412221917142

  • 有向图

image-20240412220531919

4、拓扑排序

  • 入度有箭头指向自己的为入度点

  • 出度:有指向别人的箭头

    • 如4:入度为2,出度为2
    • 如1:入度为0,出度为2
  • 拓扑排序

    没有入度的点输出,并去除所对应边,如图,因为1没有入度输出1,第二个图2没有入度输出2。如果有多个没有入度的随意选择

image-20240410184232032

  • 逆拓扑排序

    • 与拓扑排序相反,逆拓扑排序找没有出度的点
    • 如上图逆拓扑排序输出结果为:5、3、4、2、1

5、AOE网、关键路径

  • AOE网

    AOE网(Activity On Edge Network)用边表示活动,用顶点表示事件(活动的完成)。边是带权的,表示活动需要的时间。

  • 源点与汇点

    源点:入度为0的点,表示一个工程的开始。

    汇点:出度为0的点,表示一个工程的结束。

  • 关键活动与关键路径

    在AOE网中,从源点到汇点最长的路径称为关键路径,在关键路径上的活动称为关键活动

    因为AOE网中的活动是可以并行进行的,所以整个工程的时间开销,其实是最长路径的时间开销。即关键路径制约整个工程的工期。

  • 关键路径应用

    • 计算

      事件最早发生时间 = 拓扑排序第一个点到各点权重和(多路径选最大值)

      时间最迟发生时间 = 逆拓扑排序的第一个点 - 到各点权重值(多路径选做差完最小的值)

      活动最早发生时间 = 弧尾的事件最早发生时间

      活动最迟发生时间 = 弧头事件最迟发生时间 - 权重(即该边权重)

      时间余量 = 活动最迟发生时间 - 活动最早发生时间

    23249644e0add58331690dc41b8d0a2

  • 10
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
下面是一个使用邻接表拓扑排序算法关键路径的示例代码: ```c #include <stdio.h> #include <stdlib.h> #define MAX_VERTEX_NUM 100 #define INFINITY 65535 // 邻接表中的边结构体 typedef struct ArcNode { int adjvex; // 该边指向的顶点编号 int weight; // 该边的权重 struct ArcNode *next; // 指向下一条边的指针 } ArcNode; // 邻接表中的顶点结构体 typedef struct VertexNode { int data; // 顶点编号 ArcNode *firstarc; // 指向第一条边的指针 } VertexNode; // 邻接表结构体 typedef struct { VertexNode vertices[MAX_VERTEX_NUM]; // 顶点数组 int vexnum, arcnum; // 顶点数和边数 } ALGraph; // 拓扑排序中的栈结构体 typedef struct { int *data; // 存储栈中的元素 int top; // 栈顶指针 int size; // 栈的最大容量 } Stack; // 创建邻接表 void createGraph(ALGraph *G) { int i, j, k, weight; ArcNode *p; printf("请输入顶点数和边数:"); scanf("%d %d", &G->vexnum, &G->arcnum); // 初始化邻接表 for (i = 0; i < G->vexnum; i++) { G->vertices[i].data = i; G->vertices[i].firstarc = NULL; } // 读入边的信息,建立邻接表 for (k = 0; k < G->arcnum; k++) { printf("请输入边的起点、终点和权重:"); scanf("%d %d %d", &i, &j, &weight); // 添加一条从i到j的边 p = (ArcNode *)malloc(sizeof(ArcNode)); p->adjvex = j; p->weight = weight; p->next = G->vertices[i].firstarc; G->vertices[i].firstarc = p; } } // 拓扑排序 int topologicalSort(ALGraph G, int ve[]) { int i, j; int count = 0; int indegree[MAX_VERTEX_NUM] = {0}; // 存储每个顶点的入度 int *stack = (int *)malloc(sizeof(int) * G.vexnum); // 存储拓扑排序中的顶点 int top = -1; // 计算每个顶点的入度 for (i = 0; i < G.vexnum; i++) { for (ArcNode *p = G.vertices[i].firstarc; p != NULL; p = p->next) { indegree[p->adjvex]++; } } // 将入度为0的顶点入栈 for (i = 0; i < G.vexnum; i++) { if (indegree[i] == 0) { stack[++top] = i; } } // 依次弹出栈顶顶点,更新其邻接点的入度,入度为0的顶点入栈 while (top != -1) { i = stack[top--]; count++; // 更新所有以i为起点的邻接点的入度 for (ArcNode *p = G.vertices[i].firstarc; p != NULL; p = p->next) { j = p->adjvex; if (--indegree[j] == 0) { stack[++top] = j; } // 更新该邻接点的最早开始时间 if (ve[i] + p->weight > ve[j]) { ve[j] = ve[i] + p->weight; } } } if (count != G.vexnum) { return 0; // 有环 } else { return 1; // 无环 } } // 计算关键路径 void criticalPath(ALGraph G) { int i, j; int vl[MAX_VERTEX_NUM]; // 存储每个顶点的最晚开始时间 int ve[MAX_VERTEX_NUM] = {0}; // 存储每个顶点的最早开始时间 ArcNode *p; // 计算每个顶点的最早开始时间 if (!topologicalSort(G, ve)) { printf("该图存在环,无法计算关键路径!\n"); return; } // 计算每个顶点的最晚开始时间 for (i = 0; i < G.vexnum; i++) { vl[i] = ve[G.vexnum - 1]; } for (i = G.vexnum - 2; i >= 0; i--) { for (p = G.vertices[i].firstarc; p != NULL; p = p->next) { j = p->adjvex; if (vl[j] - p->weight < vl[i]) { vl[i] = vl[j] - p->weight; } } } // 输出关键路径 printf("关键路径为:"); for (i = 0; i < G.vexnum; i++) { for (p = G.vertices[i].firstarc; p != NULL; p = p->next) { j = p->adjvex; if (ve[i] + p->weight == vl[j]) { printf("%d->%d ", i, j); } } } printf("\n"); } int main() { ALGraph G; createGraph(&G); criticalPath(G); return 0; } ``` 在该示例代码中,使用拓扑排序算法计算每个顶点的最早开始时间和最晚开始时间,并根据它们的差值来找到关键路径。在实际应用中,还需要考虑多个关键路径的情况,以及如何处理并发活动等问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值