当前位置: 首页 > article >正文

数据结构6-图

图的基本概念

顶点的度

与该顶点相关联的边的数目,记为 TD(v)

在 有向图 中,顶点的度 等于该顶点的 入度 与 出度 之和。

顶点 v 的入度 是以 v 为终点 的有向边的条数,记作 ID(v)

顶点 v 的出度 是以 v 为起点 的有向边的条数,记作 OD(v)

路径:

路径、回路及相关概念

路径 连续的边构成的顶点序列。

路径长度 路径上边或弧的数量/权值之和。

回路(环) 第一个顶点和最后一个顶点相同的路径。

简单路径 除路径起点和终点可以相同外,其余顶点均不相同的路径。

简单回路(简单环) 除路径起点和终点相同外,其余顶点均不相同的路径。

路径的分类

简单路径 路径中的顶点不重复。

非简单路径 路径中存在重复的顶点。

回路(环) 起点和终点相同,形成封闭路径。

连通图:

在无向图或有向图G=(V, E)中,若对任意两个顶点v,u 都存在从 v 到 u 的路径,则称G 是连通图(强连通图)。

子图:

设有两个图 G=(V,E)、G1=(V1,E1),若 V1⊆V 且 E1⊆E,则称 G1 是 G 的子图。(b)、(c) 是 (a) 的子图

无向图连通分量:

无向图 G 的极大连通子图称为 G 的连通分量

极大连通子图:该子图是 G 的连通子图,若将 G 的任何不在该子图中的顶点加入,则子图不再连通。

左侧(非连通图)该图由两个独立的部分组成,不能通过一条路径连接所有点,因此是非连通图。

右侧(连通分量)通过连通分量的概念,将原始图拆分成两个连通的子图。一个子图包含V0,V1,V2,V3。另一个子图包含V4,V5。

有向图强连通分量:

有向图 G 的极大强连通子图称为 G 的强连通分量

极大强连通子图的意思是:该子图是 G 的强连通子图,若将 G 的任何不在该子图中的顶点加入,则子图不再是强连通的。

左侧(有向图)该图包含多个有向边,部分顶点可以互相到达,但不是所有顶点之间都存在路径。

右侧(强连通分量)该图被分成了两个强连通分量:V0,V2,V3 组成一个强连通分量,因为在这三个顶点之间,存在双向的可达路径。V1 作为单独的一个强连通分量,因为它无法到达其他顶点,其他顶点也无法到达它。

极小子图,生成树:

极小连通子图 该子图是 G 的连通子图,在该子图中删除任何一条边,子图不再连通。

生成树 包含无向图 G 所有顶点 的极小连通子图。

生成森林 对于非连通图,由各个连通分量的生成树的集合组成。

左侧(连通图)该图是连通的,所有顶点之间至少存在一条路径。

右侧(生成树)该图保留了所有顶点,但删除了一些边,使其仍然连通但不形成回路。生成树是极小的,即它不含任何多余的边,同时确保所有顶点仍然连通。

图的存储结构(数组表示法、邻接表表示法)

数组(邻接矩阵)表示法

建立一个顶点表(记录各个顶点的信息)邻接矩阵(表示各个顶点之间的关系)

邻接矩阵n×n 的矩阵:

A[i][j]=1 表示顶点 Vi​ 和 Vj​ 之间有一条边。

A[i][j]=0 表示两顶点之间没有边。

适用场景稠密图(边比较多的图)。快速查询 顶点之间是否有边(O(1) 复杂度)。

对于一个无向图 G:

G 有 n 个顶点,记作 V1​,V2​,...,Vn​

邻接矩阵 A 是一个 n×n 的方阵,其中:

无向图的邻接矩阵是对称的,即:

有无向图,邻接矩阵如下:

有向图数组表示法(邻接矩阵):

对于一个有向图 G:

G 有 n 个顶点,记作 V1,V2,...,Vn

邻接矩阵 A 是一个 n×n 的二维数组,其中:

有向图的邻接矩阵一般是不对称的,即:

有向图,邻接矩阵如下:

顶点的出度=第i行元素之和

顶点的入度=第i列元素之和

顶点的度=第i行元素之和+第i列元素之和

无向图邻接表表示法():

邻接表是一种适用于 稀疏图(边数较少)的数据结构,它使用链表存储与某个顶点相邻的顶点。

头结点(Vertex Node)每个顶点 vvv 存储在一个 一维数组 中:Vexs[n]。每个顶点都有一个头指针 firstarc,指向其 邻接点链表 的第一个节点。

表结点(Edge Node)每条边在 链表 结构中存储,每个邻接点包含:adjvex:存储相邻顶点的编号,nextarc:指向下一个邻接点(链表),info(可选):可以存储边的权值或其他信息

性质:

邻接表不唯一 因为链表存储的顺序可以不同,所以同一个图可能有不同的邻接表表示。

存储规模 若无向图有 n 个顶点,e 条边,则:头结点数量 = n,表结点数量 = 2e (因为无向图的边被存储两次)

顶点的度 顶点 vi​ 的度 等于 邻接链表中结点的个数,即:degree(vi) = 该顶点的邻接表中结点数

有向图邻接表表示法(只记录点的出度指针):

有向图的邻接表与逆邻接表的区别,以及如何通过这两种结构计算顶点的出度和入度:

邻接表

顶点 Vi​ 的出度:直接查看第 i 个链表的节点个数(即该顶点指向的顶点数量)。

顶点 Vi​ 的入度:需要遍历所有链表中值为 i−1 的节点(即其他顶点指向 Vi​ 的次数),效率较低。

逆邻接表

顶点 Vi​ 的入度:直接查看第 i 个链表的节点个数(即指向该顶点的其他顶点数量)。

顶点 Vi 的出度:需要遍历所有链表中值为 i−1 的节点,效率较低。

图的深度优先搜索和广度优先搜索算法及简单应用

深度优先搜索(无节点可遍历就回退):

DFS 的核心逻辑

深度优先:从起点出发,尽可能深地访问未探索的分支,直到尽头后回溯。

回溯机制:当某节点的所有邻接节点均被访问后,逐层返回上一层节点继续探索。

图中顶点从 V1 到 V8,可能的邻接关系如下(假设):V1 连接 V2 和 V3,V2 连接 V4 和 V5,V4 连接 V8,V5 连接 V8,V3 连接 V6 和 V7,根据邻接关系,不同分支选择会导致不同的遍历顺序:

V1 ⇒ V2 ⇒ V4 ⇒ V8 ⇒ V5 ⇒ V3 ⇒ V6 ⇒ V7,V2 优先选择 V4 分支,再回溯到 V5。

V1 ⇒ V2 ⇒ V5 ⇒ V8 ⇒ V4 ⇒ V3 ⇒ V6 ⇒ V7,V2 优先选择 V5 分支,再回溯到 V4。

V1 ⇒ V3 ⇒ V6 ⇒ V7 ⇒ V2 ⇒ V4 ⇒ V8 ⇒ V5,V1 优先选择 V3 分支,再回溯到 V2。

DFS 与树的先根遍历的关联

相似性:DFS 对连通图的遍历顺序类似于树的先根遍历(Root-L-Child-R-Child)。

差异:图可能有环,需额外标记已访问节点以避免重复遍历。

为何存在多种遍历顺序?

邻接表顺序影响:若邻接表中节点的邻接点存储顺序不同(如 V2 的邻接表为 [V4, V5] 或 [V5, V4]),遍历顺序会不同。

实现策略:递归或栈的选择逻辑可能影响分支优先级。

广度优先搜索:

广度优先遍历(BFS)的核心逻辑

逐层访问:从起点开始,先访问所有直接邻接的顶点,再依次访问下一层邻接顶点。

队列结构:使用队列保存待访问顶点,按“先进先出”顺序处理。

避免重复:需标记已访问顶点,防止重复遍历。

在用户示例中的具体表现

BFS 遍历过程:

从 V1(索引 0)入队。

访问 V1,将其邻接点 V2(索引 1)入队。

访问 V2,将其邻接点 V3(索引 2)入队。

重复此过程,直到访问完 V8(索引 7)。

遍历结果:

V1 ⇒ V2 ⇒ V3 ⇒ V4 ⇒ V5 ⇒ V6 ⇒ V7 ⇒ V8

比较复杂度:

时间复杂度

DFS:平均/最坏情况:O(V + E),其中 V 为顶点数,E 为边数。

BFS:平均/最坏情况:O(V + E),与 DFS 相同。

空间复杂度

DFS:最坏情况:O(V)(例如线性链式图)

BFS:最坏情况:O(V)(例如完全二叉树)

图遍历的应用:最小生成树、最短路径、拓扑排序、关键路径等

最小生成树:

给定无向网络,在该网的左右生成树中,使得各个边的权值之和最小的树称为最小生成树,也是最小代价生成树。

prim最小生成树:(最小生长树)

初始化:选择一个起始顶点,将其加入最小生成树中。

  • 选择一个起始顶点。假设我们选择顶点1作为起始点。

  • 将顶点1加入最小生成树中。

选择边:在所有连接已选顶点和未选顶点的边中,选择权重最小的边。

  • 在所有连接已选顶点(目前只有顶点1)和未选顶点的边中,选择权重最小的边。

  • 假设从顶点1出发,连接的边及其权重如下:

    • 1-3: 权重3

    • 1-4: 权重4

  • 选择权重最小的边1-3。

  • ......

添加顶点:将这条边连接的未选顶点加入最小生成树中。

  • 将边1-3连接的未选顶点3加入最小生成树中。

  • 现在,最小生成树包含顶点1和3。

  • ......

重复:重复步骤2和3,直到所有顶点都被包含在最小生成树中。

kruscal最小生成树:(从小到大加边不成环)

使用克鲁斯卡尔算法构造树的步骤如下:

既然需要使得构造树的路径权值最小,可先将连通图中的所有路径拿出来,按照权值从小到的顺序进行排列。

 然后后按照顺序将路径一条条地添加进节点之间,直到所有的节点都有被树连接。在这个过程中,我们需要避免环的产生,如下图,在添加权值为4的路径时,路径(1-3-4)与路径(3-4-7)均形成了环路。为了形成树,在第四步添加路径时,我们就需要通过判断成环而从树状图数据中剔除路径{1,3}及{4,7}。

比较:

思路

Prim算法:基于顶点的贪心算法。它从一个起始顶点开始,逐步扩展生成树,每次选择连接已选顶点和未选顶点的最小权重边。

Kruskal算法:基于边的贪心算法。它从所有边中选择权重最小的边,逐步构建生成树,确保不形成环。

实现

Prim算法:选择一个起始顶点。重复步骤,直到所有顶点都被包含在生成树中:选择连接已选顶点和未选顶点的最小权重边。将这条边连接的未选顶点加入生成树。

Kruskal算法:将所有边按权重从小到大排序。重复步骤,直到生成树包含所有顶点:选择当前最小权重的边。如果这条边连接的两个顶点不在同一集合中(即不形成环),则将其加入生成树,并合并这两个集合。

复杂度

Prim算法:使用二叉堆时,时间复杂度为 O(ElogV),其中 E 是边数,V 是顶点数。

Kruskal算法:时间复杂度为 O(ElogE),主要来自边的排序操作。

最短路径:

单源最短路径—用Dijkstra(迪杰斯特拉)算法:

初始化

从源点 v0​ 出发,检查所有直达路径(即通过一条边直接到达的路径)。

记录这些路径的长度(如 v0​→vk​ 的权值),未直接连接的路径视为无穷大。

选择最短路径

从所有已记录的路径中,选择当前长度最短的路径,记为 v0​→u。

此步骤确定 u 为当前离源点最近的节点,并将其标记为“已确定最短路径”。

更新路径

检查节点 u 的所有邻接节点 vk​,若存在边 (u,vk​),则尝试更新路径:

计算新路径长度:原路径(v0​→u)+边权(u→vk​)。

若该值小于当前记录的 v0​→vk​ 的路径长度,则更新为更短的路径 v0​→u→vk​。

重复上述过程,选择下一个最短路径节点,直到所有节点均被处理。

所有顶点间的最短路径—用Floyd(弗洛伊德)算法:

初始邻接矩阵

初始状态下,用一个矩阵表示各点之间的距离:

直接相连的边使用对应权值

没有直接相连的顶点间距离设为 -(无穷大)

对角线(自身到自身)设为 0

Floyd算法的执行步骤

逐步加入中间节点(A、B、C)进行松弛操作:

先检查是否通过 A 可以更新任意两点间的最短路径。

再检查是否通过 B 可以进一步优化路径。

最后检查是否通过 C 进行最终优化。

如果加入的中间节点能让路径更短,就更新矩阵;否则保持原值。

最终最短路径矩阵

经过所有步骤后,矩阵中的值就是所有顶点对之间的 最短路径长度。

右侧的路径表格 记录了最短路径的具体走法,例如:

AB → ABC

CA → CAB

BCA(路径变更后出现)

拓扑排序:

AOV网(Activity On Vertex Network)

定义:使用 有向图 来表示一个工程的各个子工程及其相互制约的关系。

弧(边)表示活动之间的优先约束关系,即哪些活动必须先完成,哪些活动可以同时进行。

应用:适用于 拓扑排序,用于确定任务的执行顺序。

aov拓扑排序(成环检测):

拓扑排序的步骤

  • 选择一个没有前驱(入度为 0)的顶点,并输出。
  • 删除该顶点及所有以它为起点的边,更新图结构。
  • 重复以上步骤,直到所有顶点都被输出。
  • 如果图中仍然存在入度不为 0 的顶点,说明图中存在环,无法进行拓扑排序。

拓扑排序结果结果之一:C1, C2, C3, C4, C5, C7, C9, C10, C11, C6, C12, C8

不同的拓扑排序可能不唯一,这取决于:选择无前驱顶点的顺序不同。如果有多个无前驱顶点,可以按任意顺序选取,因此可能得到多个不同的排序结果。

关键路径:

AOE网(Activity On Edge Network)

定义:也是使用 有向图 表示工程及其约束关系,但方式不同。

顶点表示活动的起点或终点事件,即一个活动的开始和结束时刻。

应用:适用于 关键路径法(CPM),用于计算项目的最短完成时间。

aoe关键路径:

步骤计算公式计算方式
计算 veve(j)=max(ve(i)+wi,j​)从起点 向前推进
计算 vlvl(i)=min(vl(j)−wi,j​)从终点 向后回溯
计算 e(i)e(i)=ve(j)任务最早开始时间
计算 l(i)l(i)=vl(k)−wj,k​任务最晚开始时间
计算松弛时间S(i)=l(i)−e(i)S = 0 表示关键任务
找关键路径关键活动的集合形成 关键路径
1. 构建 AOE 网

以 顶点表示事件(起点或终点),边表示活动(任务),边的 权值表示任务的持续时间。

构造 AOE 网络的有向图,确保任务依赖关系正确。

2. 计算各顶点的最早开始时间ET

设起始事件的 最早开始时间 ET(0)=0。

依次遍历 所有入度为 0 的节点,沿拓扑排序的顺序 向前推进:

ET(i) 是起点事件的最早开始时间。

wij 是任务的持续时间。

取最大值 确保任务完成后才能进入下一个事件。

计算所有节点的最早发生时间 ET 值,终点事件的 ET 值就是整个项目的最短完成时间。

3. 计算各顶点的最晚开始时间LT

设终点事件的 最晚完成时间 LT(n)=ET(n)。

依次按照 拓扑排序的逆序 向后推算: 

LT(j)是终点事件的最晚开始时间。

取最小值,确保任务必须在最迟时间开始,否则会影响整体进度。

4. 计算每条活动的松弛时间S

松弛时间(Slack)是指任务可以 延迟多少时间 而不会影响项目完工:

如果 S(i,j)=0,说明该任务必须准时完成,否则项目会被延误。

关键路径上的任务满足 S(i,j)=0。

5. 确定关键路径

找出所有松弛时间 S(i,j)=0 的任务,即关键任务。

连接这些任务形成关键路径,该路径上的任务 决定了项目的最短完成时间。

路径 0 → 2 → 3 → 4 是关键路径

项目最短完成时间 = 12 天


http://www.kler.cn/a/612035.html

相关文章:

  • 数制——FPGA
  • C++ set容器总结
  • Linux 目录结构(文件系统结构)示例说明
  • 《Spring Cloud Eureka 高可用集群实战:从零构建 99.99% 可靠性的微服务注册中心》
  • 【后端】CDN内容分发网络
  • 美摄科技智能汽车视频延迟摄影解决方案,开启智能出行新视界
  • ESP32S3 WIFI 实现TCP服务器和静态IP
  • 使用 OCRmyPDF 将扫描 PDF 转为可搜索文档和文本文件
  • <sa8650>QCX Camera Channel configuration
  • 如何根据目标网站调整Python爬虫的延迟时间?
  • Postman 版本信息速查:快速定位版本号
  • 量子计算模拟中的测量与噪声建模:基于 3 量子比特系统分析
  • 甘肃旅游服务平台+论文源码视频演示
  • 算法每日一练 (20)
  • 容器C++
  • 关于优麒麟ukylin如何更换清华源以及ubuntu24.04安装gcc-i686-linux-gnu找不到包的问题
  • C#中3维向量的实现
  • 【商城实战(74)】数据采集与整理,夯实电商运营基石
  • 使用crontab 每两分钟执行一次 进入 /var/xxx 执行 git pull
  • 力扣 --2712. 使所有字符相等的最小成本