26考研——图_图的应用(6)

408答疑


文章目录

  • 四、图的应用
    • 图的应用考查形式
    • 最小生成树
      • 最小生成树概念
      • 最小生成树的性质
      • 最小生成树中某顶点到其他顶点是否具有最短路径的分析
      • 构造最小生成树的算法
        • Prim 算法
          • Prim 算法概述
          • Prim 算法的构建思想
          • Prim 算法的步骤
          • Prim 算法的示例
          • Prim 算法的性质
        • Kruskal 算法
          • Kruskal 算法概述
          • Kruskal 算法的构建思想
          • Kruskal 算法的步骤
          • Kruskal 算法的示例
          • Kruskal 算法的性质
    • 最短路径
      • 概述
      • 最短路径问题的分类
      • Dijkstra 算法求单源最短路径问题
        • Dijkstra算法的基本原理
        • Dijkstra算法的步骤
        • 实例说明
          • 每轮得到的最短路径
        • Dijkstra算法的性质
        • Dijkstra算法的时间复杂度
        • 注意事项
      • Floyd 算法求各顶点之间最短路径问题
        • 问题描述
        • Floyd 算法的基本思想
        • 算法描述
        • 实例说明
        • 算法复杂度
        • 应用
      • BFS 算法、Diikstra 算法和 Floyd 算法求最短路径的总结
    • 有向无环图描述表达式
      • 有向无环图 (DAG)
      • 构建表达式的有向无环图
      • 注意事项
    • 拓扑排序
      • 相关概念
        • 用途
        • AOV 网
      • 拓扑排序的步骤
        • 拓扑排序的实例
      • 不同存储方式下的拓扑排序的效率
      • DFS 实现拓扑排序的思想
      • 逆拓扑排序
        • 定义
        • 应用
      • 注意事项
    • 关键路径
    • 不同存储结构时各种图算法的时间复杂度
  • 六、参考资料
    • 鲍鱼科技课件
    • 26王道考研书


四、图的应用

图的应用是历年考研考查的重点。

图的应用考查形式

一般而言,这部分内容直接以算法设计题形式考查的可能性很小,更多的是结合图的实例来考查算法的具体操作过程。因此,图的应用要求至少掌握手工模拟图的各个算法的执行过程。此外,还需掌握对给定模型建立相应的图去解决问题的方法。

最小生成树

最小生成树问题是指在图中找到一个包含所有顶点的最小权重的边的集合,使得这些边构成一棵树。这个问题在网络设计、电路板设计等领域有广泛应用。

最小生成树概念

最小生成树(Minimum Spanning Tree, MST)是一个连通图的生成树,包含图中的所有顶点,并且只含尽可能少的边。最小生成树可以用Prim(普里姆)算法或Kruskal(克鲁斯卡尔)算法求出。

最小生成树的性质

  1. 唯一性:若图 G G G 中存在权值相同的边,则 G G G 的最小生成树可能不唯一,即最小生成树的树形不唯一。当图 G G G 中的各边权值互不相等时, G G G 的最小生成树是唯一的;若无向连通图 G G G 的边数比顶点数少 1,即 G G G 本身是一棵树时,则 G G G 的最小生成树就是它本身。
  2. 权值之和:虽然最小生成树不唯一,但其对应的边的权值之和总是唯一的,而且是最小的。
  3. 边数:最小生成树的边数为顶点数减 1。

最小生成树中某顶点到其他顶点是否具有最短路径的分析

最小生成树中所有边的权值之和最小,但不能保证任意两个顶点之间的路径是最短路径。例如,最小生成树中 A A A C C C 的路径长度为 5,但图中 A A A C C C 的最短路径长度为 4。

在这里插入图片描述

构造最小生成树的算法

构造最小生成树有多种算法,但大多数算法都利用了最小生成树的下列性质:假设 G = ( V , E ) G = (V, E) G=(V,E) 是一个带权连通无向图, U U U 是顶点集 V V V 的一个非空子集。若 ( u , v ) (u, v) (u,v) 是一条具有最小权值的边,其中 u ∈ U u \in U uU v ∈ V − U v \in V - U vVU,则必存在一棵包含边 ( u , v ) (u, v) (u,v) 的最小生成树。

基于该性质的最小生成树算法主要有 Prim 算法和 Kruskal 算法,它们都基于贪心算法的策略。对这两种算法应主要掌握算法的本质含义和基本思想,并能动手模拟算法的实现步骤。

Prim 算法
Prim 算法概述

Prim(普里姆)算法是从顶点的方面考虑构建最小生成树(MST)的一种贪心算法。其执行过程类似于寻找图的最短路径的Dijkstra算法。

Prim 算法的构建思想
  • 设图 G G G 顶点集合为 U U U,首先任意选择图 G G G 中的一点作为起始点 a a a,将该点加入集合 V V V,再从集合 U − V U-V UV 中找到另一点 b b b 使得点 b b b V V V 中任意一点的权值最小,此时将 b b b 点也加入集合 V V V;以此类推,现在的集合 V = { a , b } V=\{a, b\} V={a,b},再从集合 U − V U-V UV 中找到另一点 c c c 使得点 c c c V V V 中任意一点的权值最小,此时将 c c c 点加入集合 V V V,直至所有顶点全部被加入 V V V,此时就构建出了一颗MST。

  • 因为有 N N N 个顶点,所以该MST就有 N − 1 N-1 N1 条边,每一次向集合 V V V 中加入一个点,就意味着找到一条MST的边。

Prim 算法的步骤
  1. 初始化:向空树 T = ( U , E T ) T=(U, E_T) T=(U,ET) 中添加图 G = ( V , E ) G=(V, E) G=(V,E) 的任意一个顶点 u 0 u_0 u0,使 U = { u 0 } U=\{u_0\} U={u0} E T = ∅ E_T=\emptyset ET=
  2. 循环(重复下列操作直至 U = V U=V U=V):从图 G G G 中选择满足 { ( u , v ) ∣ u ∈ U , v ∈ V − U } \{(u, v) | u \in U, v \in V-U\} {(u,v)uU,vVU} 且具有最小权值的边 ( u , v ) (u, v) (u,v),加入树 T T T,置 U = U ∪ { v } U=U \cup \{v\} U=U{v} E T = E T ∪ { ( u , v ) } E_T=E_T \cup \{(u, v)\} ET=ET{(u,v)}
Prim 算法的示例

下图展示了 Prim 算法构造最小生成树的过程,从顶点 V 1 V_1 V1 开始,逐步添加顶点和边,直到所有顶点都被包含在内,形成最小生成树。

在这里插入图片描述

Prim 算法的性质

Prim算法的时间复杂度为 O ( ∣ V ∣ 2 ) O(|V|^2) O(V2),不依赖于 ∣ E ∣ |E| E,因此它适用于求解边稠密的图的最小生成树。虽然采用其他方法能改进Prim算法的时间复杂度,但增加了实现的复杂性。

Kruskal 算法
Kruskal 算法概述

Kruskal算法是从边的方面考虑构建最小生成树(MST)的一种贪心算法。与Prim算法从顶点开始扩展最小生成树不同,Kruskal算法是一种按权值的递增次序选择合适的边来构造最小生成树的方法。

Kruskal 算法的构建思想
  • 初始时为只有 n n n 个顶点而无边的非连通图 T = { V , { } } T = \{V, \{\}\} T={V,{}},每个顶点自成一个连通分量。
  • 然后按照边的权值由小到大的顺序,不断选取当前未被选取过且权值最小的边,若该边依附的顶点落在 T T T 中不同的连通分量上(使用并查集判断这两个顶点是否属于同一棵集合树),则将此边加入 T T T,否则舍弃此边而选择下一条权值最小的边。以此类推,直至 T T T 中所有顶点都在一个连通分量上。
Kruskal 算法的步骤
  1. 初始化 U = V , E T = ∅ U=V, E_T=\emptyset U=V,ET=。即每个顶点构成一棵独立的树, T T T 此时是一个仅含 ∣ V ∣ |V| V 个顶点的森林。
  2. 循环(重复直至 T T T 是一棵树):按 G G G 的边的权值递增顺序依次从 E − E T E-E_T EET 中选择一条边,若这条边加入 T T T 后不构成回路,则将其加入 E T E_T ET,否则舍弃,直到 E T E_T ET 中含有 n − 1 n-1 n1 条边。
Kruskal 算法的示例

在这里插入图片描述

Kruskal 算法的性质

根据图的相关性质,若一条边连接了两棵不同树中的顶点,则对这两棵树来说,它必定是连通的,将这条边加入森林中,完成两棵树的合并,直到整个森林合并成一棵树。

在Kruskal算法中,最坏情况需要对 ∣ E ∣ |E| E条边各扫描一次。通常采用堆来存放边的集合,每次选择最小权值的边需要 O ( log ⁡ 2 ∣ E ∣ ) O(\log_2|E|) O(log2E) 的时间;每次使用并查集来快速判断两个顶点是否属于一个集合所需的时间为 O ( α ( ∣ V ∣ ) ) O(\alpha(|V|)) O(α(V)) α ( ∣ V ∣ ) \alpha(|V|) α(V) 的增长极其缓慢,可视为常数。算法的总时间复杂度为 O ( ∣ E ∣ log ⁡ 2 ∣ E ∣ ) O(|E|\log_2|E|) O(Elog2E),不依赖于 ∣ V ∣ |V| V,因此 Kruskal 算法适合于边稀疏而顶点较多的图。

最短路径

最短路径问题是指在图中找到从一个顶点到另一个顶点的路径,使得路径的总权重最小。这个问题在路由选择、物流配送等领域有重要应用。

概述

图的最短路径问题是指,在有向图中找到一条从源点到达终点的路径,这条路径上的边权和总和达到最小,这意味着,如果存在多条路径可以从源点到达终点,最短路径是指这些路径中权值总和最小的那一条。

最短路径问题的分类

带权有向图 G G G 的最短路径问题一般可分为两类:

  1. 单源最短路径:即求图中某一顶点到其他各顶点的最短路径,可通过经典的 Dijkstra(迪杰斯特拉)算法求解;
  2. 每对顶点间的最短路径:可通过 Floyd(弗洛伊德)算法来求解。

求解最短路径的算法通常都依赖于一种性质,即两点之间的最短路径也包含了路径上其他顶点间的最短路径。

Dijkstra 算法求单源最短路径问题

Dijkstra算法是一种用于求解单源最短路径问题的算法,适用于带权有向图或带权无向图。该算法通过逐步扩展已知最短路径的顶点集合来找到从源点到图中所有其他顶点的最短路径。

Dijkstra算法的基本原理

Dijkstra算法设置一个集合 S S S 记录已求得的最短路径的顶点,初始时把源点 v 0 v_0 v0 放入 S S S,集合 S S S 每并入一个新顶点 v i v_i vi,都要修改源点 v 0 v_0 v0 到集合 V − S V-S VS 中顶点当前的最短路径长度值。

在构造的过程中还设置了三个辅助数组:

  • final[]:标记各顶点是否已找到最短路径,即是否归入集合 S S S
  • dist[]:记录从源点 v 0 v_0 v0 到其他各顶点当前的最短路径长度, d i s t [ i ] dist[i] dist[i] 的初始值为:若从 v 0 v_0 v0 v i v_i vi 有弧,则 d i s t [ i ] dist[i] dist[i] 为弧上的权值;否则置 d i s t [ i ] dist[i] dist[i] ∞ \infty
  • path[]:记录从源点到顶点 i i i 之间的最短路径的前驱结点。在算法结束时,可根据其值追溯得到源点 v 0 v_0 v0 到顶点 v i v_i vi 的最短路径。
Dijkstra算法的步骤

假设从顶点 v 0 v_0 v0 出发,即 v 0 = 0 v_0=0 v0=0,集合 S S S 最初只包含顶点 0 0 0,邻接矩阵 a r c s arcs arcs 表示带权有向图, a r c s [ i ] [ j ] arcs[i][j] arcs[i][j] 表示有向边 < i , j > <i,j> <i,j> 的权值,若不存在有向边 < i , j > <i,j> <i,j>,则 a r c s [ i ] [ j ] arcs[i][j] arcs[i][j] ∞ \infty

Dijkstra算法的步骤如下(不考虑对 p a t h [ ] path[] path[] 的操作):

  1. 初始化:集合 S S S 初始化为 { 0 } \{0\} {0} d i s t [ ] dist[] dist[] 的初始值 d i s t [ 0 ] [ i ] = a r c s [ 0 ] [ i ] , i = 1 , 2 , . . . , n − 1 dist[0][i]=arcs[0][i], i=1,2,...,n-1 dist[0][i]=arcs[0][i],i=1,2,...,n1
  2. 从顶点集合 V − S V-S VS 中选出 v j v_j vj,满足 d i s t [ j ] = min ⁡ { d i s t [ j ] ∣ v j ∈ V − S } dist[j]=\min\{dist[j] | v_j \in V-S\} dist[j]=min{dist[j]vjVS} v j v_j vj 就是当前求得的一条从 v 0 v_0 v0 出发的最短路径的终点,令 S = S ∪ { v j } S=S \cup \{v_j\} S=S{vj}
  3. 修改从 v 0 v_0 v0 出发到集合 V − S V-S VS 上任意一个顶点 v k v_k vk 可达的最短路径长度:若 d i s t [ j ] + a r c s [ j ] [ k ] < d i s t [ k ] dist[j]+arcs[j][k]<dist[k] dist[j]+arcs[j][k]<dist[k],则更新 d i s t [ k ] = d i s t [ j ] + a r c s [ j ] [ k ] dist[k]=dist[j]+arcs[j][k] dist[k]=dist[j]+arcs[j][k]
  4. 重复步骤 2)~3)操作共 n − 1 n-1 n1,直到所有的顶点都包含在集合 S S S 中。
实例说明

对下图中的图应用 Dijkstra 算法求从顶点 1 出发至其余顶点的最短路径的过程,算法执行过程的说明如下:

在这里插入图片描述

  • 初始化:集合 S S S 初始为 { v 1 } \{v_1\} {v1} v 1 v_1 v1 可达 v 2 v_2 v2 v 5 v_5 v5 v 1 v_1 v1 不可达 v 3 v_3 v3 v 4 v_4 v4,因此 d i s t [ ] dist[] dist[] 数组各元素的初始值依次设置为 d i s t [ 2 ] = 10 dist[2]=10 dist[2]=10 d i s t [ 3 ] = ∞ dist[3]=\infty dist[3]= d i s t [ 4 ] = ∞ dist[4]=\infty dist[4]= d i s t [ 5 ] = 5 dist[5]=5 dist[5]=5

  • 第1轮:选出最小值 d i s t [ 5 ] dist[5] dist[5],将顶点 v 5 v_5 v5 并入集合 S S S,即此时已找到 v 1 v_1 v1 v 5 v_5 v5 的最短路径。当 v 5 v_5 v5 加入 S S S 后,从 v 1 v_1 v1 到集合 V − S V-S VS 中可达顶点的最短路径长度可能会产生变化。因此需要更新 d i s t [ ] dist[] dist[] 数组。 v 5 v_5 v5 可达 v 2 v_2 v2,因 v 1 → v 5 → v 2 v_1 \to v_5 \to v_2 v1v5v2 的距离 8 比 d i s t [ 2 ] = 10 dist[2]=10 dist[2]=10 小,更新 d i s t [ 2 ] = 8 dist[2]=8 dist[2]=8 v 5 v_5 v5 可达 v 3 v_3 v3 v 1 → v 5 → v 3 v_1 \to v_5 \to v_3 v1v5v3 的距离 14,更新 d i s t [ 3 ] = 14 dist[3]=14 dist[3]=14 v 5 v_5 v5 可达 v 4 v_4 v4 v 1 → v 5 → v 4 v_1 \to v_5 \to v_4 v1v5v4 的距离 7,更新 d i s t [ 4 ] = 7 dist[4]=7 dist[4]=7

  • 第 2 轮:选出最小值 d i s t [ 4 ] dist[4] dist[4],将顶点 v 4 v_4 v4 并入集合 S S S。继续更新 d i s t [ ] dist[] dist[] 数组。 v 4 v_4 v4 不可达 v 2 v_2 v2 d i s t [ 2 ] dist[2] dist[2] 不变; v 4 v_4 v4 可达 v 3 v_3 v3 v 1 → v 5 → v 4 → v 3 v_1 \to v_5 \to v_4 \to v_3 v1v5v4v3 的距离 13 比 d i s t [ 3 ] dist[3] dist[3] 小,故更新 d i s t [ 3 ] = 13 dist[3]=13 dist[3]=13

  • 第 3 轮:选出最小值 d i s t [ 2 ] dist[2] dist[2],将顶点 v 2 v_2 v2 并入集合 S S S。继续更新 d i s t [ ] dist[] dist[] 数组。 v 2 v_2 v2 可达 v 3 v_3 v3 v 1 → v 5 → v 2 → v 3 v_1 \to v_5 \to v_2 \to v_3 v1v5v2v3 的距离 9 比 d i s t [ 3 ] dist[3] dist[3] 小,更新 d i s t [ 3 ] = 9 dist[3]=9 dist[3]=9

  • 第 4 轮:选出唯一最小值 d i s t [ 3 ] dist[3] dist[3],将顶点 v 3 v_3 v3 并入集合 S S S,此时全部顶点都已包含在 S S S 中。

下表为从 v 1 v_1 v1到各终点的 dist 值和最短路径的求解过程

顶点第1轮第2轮第3轮第4轮
210
v 1 → v 2 v_1 \rightarrow v_2 v1v2
8
v 1 → v 5 → v 2 v_1 \rightarrow v_5 \rightarrow v_2 v1v5v2
8
v 1 → v 5 → v 2 v_1 \rightarrow v_5 \rightarrow v_2 v1v5v2
314
v 1 → v 5 → v 3 v_1 \rightarrow v_5 \rightarrow v_3 v1v5v3
13
v 1 → v 5 → v 4 → v 3 v_1 \rightarrow v_5 \rightarrow v_4 \rightarrow v_3 v1v5v4v3
9
v 1 → v 5 → v 2 → v 3 v_1 \rightarrow v_5 \rightarrow v_2 \rightarrow v_3 v1v5v2v3
47
v 1 → v 5 → v 4 v_1 \rightarrow v_5 \rightarrow v_4 v1v5v4
55
v 1 → v 5 v_1 \rightarrow v_5 v1v5
集合S{1, 5}{1, 5, 4}{1, 5, 4, 2}{1, 5, 4, 2, 3}
每轮得到的最短路径
  • 第1轮:1→5,路径距离为 5
  • 第2轮:1→5→4,路径距离为 7
  • 第3轮:1→5→2,路径距离为 8
  • 第4轮:1→5→2→3,路径距离为 9

显然,Dijkstra 算法也是基于贪心策略的。

Dijkstra算法的性质
  • Dijkstra算法的流程、操作与Prim算法的都很相似,都基于贪心策略。区别在于,目的不同:Dijkstra算法的目的是构建单源点的最短路径树;Prim算法的目的是构建最小生成树。
  • 算法思路略有不同:Prim算法从一个点开始,每次选择权值最小的边,将其连接到已构建的生成树上,直至所有顶点都已加入;而Dijkstra算法每次找出到源点距离最近且未归入集合的点,并把它归入集合,同时以这个点为基础更新从源点到其他所有顶点的距离。
  • 适用的图不同:Prim算法只能用于带权无向图。Dijkstra算法可用于带权有向图或带权无向图。
Dijkstra算法的时间复杂度
  • 使用邻接矩阵表示时,时间复杂度为 O ( ∣ V ∣ 2 ) O(|V|^2) O(V2)
  • 使用带权的邻接表表示时,虽然修改 d i s t [ ] dist[] dist[] 的时间可以减少,但因为在 d i s t [ ] dist[] dist[] 中选择最小分量的时间不变,所以时间复杂度仍为 O ( ∣ V ∣ 2 ) O(|V|^2) O(V2)
注意事项
  • 边上带有负权值时,Dijkstra算法并不适用。若允许边上带有负权值,则在与集合 S S S(已求得最短路径的顶点集,归入 S S S 内的顶点的最短路径不再变更)内某顶点(记为 a a a)以负边相连的顶点(记为 b b b)确定其最短路径时,其最短路径长度加上这条负边的权值结果可能小于 a a a 原先确定的最短路径长度,而此时 a a a 在 Dijkstra算法下是无法更新的。例如,对于下图所示的带权有向图,利用 Dijkstra算法不一定能得到正确的结果。

在这里插入图片描述

Floyd 算法求各顶点之间最短路径问题

问题描述

求所有顶点之间的最短路径问题描述如下:已知一个各边权值均大于0的带权有向图,对任意两个顶点 v i v_i vi v j v_j vj,要求求出 v i v_i vi v j v_j vj之间的最短路径和最短路径长度。

Floyd 算法的基本思想

递推产生一个 n n n阶方阵序列 A ( − 1 ) , A ( 0 ) , ⋯ , A ( k ) , ⋯ , A ( n − 1 ) A^{(-1)}, A^{(0)}, \cdots, A^{(k)}, \cdots, A^{(n-1)} A(1),A(0),,A(k),,A(n1),其中 A ( k ) [ i ] [ j ] A^{(k)}[i][j] A(k)[i][j]表示从顶点 v i v_i vi到顶点 v j v_j vj的路径长度, k k k表示系统行第 k k k个顶点的运算步骤。初始时,对于任意两个顶点 v i v_i vi v j v_j vj,若它们之间存在边,则以此边上的权值作为它们之间的最短路径长度;若它们之间不存在有向边,则以 ∞ \infty 作为它们之间的最短路径长度。以后逐步尝试在原路径中加入顶点 k k k k = 0 , 1 , ⋯ , n − 1 k = 0, 1, \cdots, n-1 k=0,1,,n1)作为中间顶点。若增加中间顶点后,得到的路径比原来的路径长度减少了,则以此新路径代替原路径。

算法描述

定义一个 n n n阶方阵序列 A ( − 1 ) , A ( 0 ) , ⋯ , A ( n − 1 ) A^{(-1)}, A^{(0)}, \cdots, A^{(n-1)} A(1),A(0),,A(n1),其中,

A ( − 1 ) [ i ] [ j ] = arcs [ i ] [ j ] A^{(-1)}[i][j] = \text{arcs}[i][j] A(1)[i][j]=arcs[i][j]

A ( k ) [ i ] [ j ] = min ⁡ { A ( k − 1 ) [ i ] [ j ] , A ( k − 1 ) [ i ] [ k ] + A ( k − 1 ) [ k ] [ j ] } , k = 0 , 1 , ⋯ , n − 1 A^{(k)}[i][j] = \min\{A^{(k-1)}[i][j], A^{(k-1)}[i][k] + A^{(k-1)}[k][j]\}, \quad k = 0, 1, \cdots, n-1 A(k)[i][j]=min{A(k1)[i][j],A(k1)[i][k]+A(k1)[k][j]},k=0,1,,n1

式中, A ( 0 ) [ i ] [ j ] A^{(0)}[i][j] A(0)[i][j]是从顶点 v i v_i vi v j v_j vj,中间顶点是 v 0 v_0 v0的最短路径的长度, A ( k ) [ i ] [ j ] A^{(k)}[i][j] A(k)[i][j]是从顶点 v i v_i vi v j v_j vj,中间顶点的序号不大于 k k k的最短路径的长度。Floyd算法是一个迭代的过程,每迭代一次,在从 v i v_i vi v j v_j vj的最短路径上就多考虑了一个顶点:经过 n n n次迭代后,所得到的 A ( n − 1 ) [ i ] [ j ] A^{(n-1)}[i][j] A(n1)[i][j]就是 v i v_i vi v j v_j vj的最短路径长度,即方阵 A ( n − 1 ) A^{(n-1)} A(n1)中就保存了任意一对顶点之间的最短路径长度。

实例说明

下图所示为带权有向图 G 及其邻接矩阵,应用 Floyd 算法执行过程的说明如下:

在这里插入图片描述

  • 初始化:方阵 A ( − 1 ) [ i ] [ j ] = arcs [ i ] [ j ] A^{(-1)}[i][j] = \text{arcs}[i][j] A(1)[i][j]=arcs[i][j]

  • 第 1 轮:将 v 0 v_0 v0 作为中间顶点,对于所有顶点对 { i , j } \{i, j\} {i,j},若有 A − 1 [ i ] [ j ] > A − 1 [ i ] [ 0 ] + A − 1 [ 0 ] [ j ] A^{-1}[i][j] > A^{-1}[i][0] + A^{-1}[0][j] A1[i][j]>A1[i][0]+A1[0][j],则将 A − 1 [ i ] [ j ] A^{-1}[i][j] A1[i][j] 更新为 A − 1 [ i ] [ 0 ] + A − 1 [ 0 ] [ j ] A^{-1}[i][0] + A^{-1}[0][j] A1[i][0]+A1[0][j]

    • A − 1 [ 2 ] [ 1 ] > A − 1 [ 2 ] [ 0 ] + A − 1 [ 0 ] [ 1 ] = 11 A^{-1}[2][1] > A^{-1}[2][0] + A^{-1}[0][1] = 11 A1[2][1]>A1[2][0]+A1[0][1]=11,更新 A − 1 [ 2 ] [ 1 ] = 11 A^{-1}[2][1] = 11 A1[2][1]=11,更新后的方阵标记为 A 0 A^0 A0
  • 第 2 轮:将 v 1 v_1 v1 作为中间顶点,继续检测全部顶点对 { i , j } \{i, j\} {i,j}

    • A 0 [ 0 ] [ 2 ] > A 0 [ 0 ] [ 1 ] + A 0 [ 1 ] [ 2 ] = 10 A^0[0][2] > A^0[0][1] + A^0[1][2] = 10 A0[0][2]>A0[0][1]+A0[1][2]=10,更新 A 0 [ 0 ] [ 2 ] = 10 A^0[0][2] = 10 A0[0][2]=10,更新后的方阵标记为 A 1 A^1 A1
  • 第 3 轮:将 v 2 v_2 v2 作为中间顶点,继续检测全部顶点对 { i , j } \{i, j\} {i,j}

    • A 1 [ 1 ] [ 0 ] > A 1 [ 1 ] [ 2 ] + A 1 [ 2 ] [ 0 ] = 9 A^1[1][0] > A^1[1][2] + A^1[2][0] = 9 A1[1][0]>A1[1][2]+A1[2][0]=9,更新 A 1 [ 1 ] [ 0 ] = 9 A^1[1][0] = 9 A1[1][0]=9,更新后的方阵标记为 A 2 A^2 A2。此时 A 2 A^2 A2 中保存的就是任意顶点对的最短路径长度。
Floyd算法的执行过程
AA(-1)A(0)A(1)A(2)
V0V1V2V0V1V2V0V1V2V0V1V2
V00613061306100610
V1100410041004904
V250511051105110

说明

  1. 表格展示了 Floyd 算法迭代过程中各阶段的邻接矩阵:
    • A A A 为初始邻接矩阵
    • A ( k ) A^{(k)} A(k) 表示经过顶点 V k V_k Vk 计算后的结果
  2. 数值变化示例:
    • A ( 1 ) A^{(1)} A(1) V 0 → V 2 V_0 \to V_2 V0V2 的路径从 13 缩短为 10(通过 V 1 V_1 V1
    • A ( 2 ) A^{(2)} A(2) V 1 → V 0 V_1 \to V_0 V1V0 的路径从 10 缩短为 9(通过 V 2 V_2 V2
  3. ∞ \infty 表示两顶点间无直接路径。
算法复杂度

Floyd算法的时间复杂度为 O ( ∣ V ∣ 3 ) O(|V|^3) O(V3)。不过由于其代码很紧凑,且并不包含其他复杂的数据结构,因此隐含的常数系数是很小的,即使对于中等规模的输入来说,它仍然是相当有效的。

Floyd算法允许图中有带负权值的边,但不允许有包含带负权值的边组成的回路。Floyd算法同样适用于带权无向图,因为带权无向图可视为权值相同往返二重边的有向图。

应用

也可以用单源最短路径算法来解决每对顶点之间的最短路径问题。轮流将每个顶点作为源点,并且在所有边权值均非负时,运行一次Dijkstra算法,其时间复杂度为 O ( ∣ V ∣ 2 ) ⋅ ∣ V ∣ = O ( ∣ V ∣ 3 ) O(|V|^2) \cdot |V| = O(|V|^3) O(V2)V=O(V3)

BFS 算法、Diikstra 算法和 Floyd 算法求最短路径的总结

算法用途无权图带权图带负权值的图带负权回路的图时间复杂度
BFS算法求单源最短路径适用不适用不适用不适用 O ( O( O(| V V V| 2 ) ^2) 2) O ( O( O(| V V V|+| E E E| ) ) )
Dijkstra算法求单源最短路径适用适用不适用不适用 O ( O( O(| V V V| 2 ) ^2) 2)
Floyd算法求各顶点之间的最短路径适用适用适用不适用 O ( O( O(| V V V| 3 ) ^3) 3)

有向无环图描述表达式

有向无环图 (DAG)

有向无环图:若一个有向图中不存在环,则称为有向无环图,简称DAG图。

构建表达式的有向无环图

有向无环图是描述含有公共子式的表达式的有效工具。例如表达式 ( ( a + b ) ∗ ( b ∗ ( c + d ) ) + ( c + d ) ∗ e ) ∗ ( ( c + d ) ∗ e ) ) ((a + b) * (b * (c + d)) + (c + d) *e)* ((c + d) * e)) ((a+b)(b(c+d))+(c+d)e)((c+d)e)) 可以用二叉树来表示,如下图所示。

在这里插入图片描述

仔细观察该表达式,可发现有一些相同的子表达式 ( c + d ) (c + d) (c+d) ( c + d ) ∗ e (c + d) * e (c+d)e,而在二叉树中,它们也重复出现。若利用有向无环图,则可实现对相同子式的共享,从而节省存储空间,下图所示为该表达式的有向无环图表示。

在这里插入图片描述

注意事项

在表达式的有向无环图表示中,不可能出现重复的操作数顶点。

拓扑排序

拓扑排序是指对一个有向无环图(DAG)的顶点进行排序,使得对于每一对有向边 ( u , v ) (u, v) (u,v),顶点 u u u 在顶点 v v v 之前。拓扑排序在任务调度、课程安排等领域有广泛应用。

相关概念

拓扑排序是一个有向无环图(DAG, Directed Acyclic Graph)的所有顶点的线性序列。且该序列必须满足下面两个条件:

  1. 每个顶点出现且只出现一次。
  2. 若存在一条从顶点 A A A 到顶点 B B B 的路径,那么在序列中顶点 A A A 必须出现在顶点 B B B 的前面。

或者可以定义为:拓扑排序是对有向无环图的顶点的一种排序,它使得若存在一条从顶点 A A A 到顶点 B B B 的路径,则在排序中 B B B 出现在 A A A 的后面。每个 AOV 网都有一个或多个拓扑排序序列。

用途

拓扑排序主要用于:

  • 判断图是否有回路。
  • 用来规定顶点活动的开展顺序。
AOV 网

AOV 网:若用有向无环图表示一个工程,其顶点表示活动,用有向边 < V i , V j > <V_i, V_j> <Vi,Vj> 表示活动 V i V_i Vi 必须先于活动 V j V_j Vj 进行的这样一种关系,则将这种有向图称为顶点表示活动的网络,简称 AOV 网。在 AOV 网中,活动 V i V_i Vi 是活动 V j V_j Vj 的直接前驱, V j V_j Vj V i V_i Vi 的直接后继,这种前驱和后继关系具有传递性,且任何活动 V i V_i Vi 不能以它自己作为自己的前驱或后继。

拓扑排序的步骤

对一个 AOV 网进行拓扑排序的算法有很多,下面介绍比较常用的一种方法的步骤:

  1. 从 AOV 网中选择一个没有前驱(入度为 0)的顶点并输出。
  2. 从网中删除该顶点和所有以它为起点的有向边。
  3. 重复步骤 1 和 2 直到当前的 AOV 网为空或当前网中不存在无前驱的顶点为止。后一种情况说明有向图中必然存在环。
拓扑排序的实例

下图所示为拓扑排序过程的示例。每轮选择一个入度为 0 的顶点并输出,然后删除该顶点和所有以它为起点的有向边,最后得到拓扑排序的结果为 {1, 2, 4, 3, 5}。

在这里插入图片描述

结点号12345
初始入度01222
第1轮0212
第2轮102
第3轮01
第4轮0

不同存储方式下的拓扑排序的效率

  • 因为输出每个顶点的同时还要删除以它为起点的边,所以采用邻接表存储时拓扑排序的时间复杂度为 O ( ∣ V ∣ + ∣ E ∣ ) O(|V| + |E|) O(V+E),采用邻接矩阵存储时拓扑排序的时间复杂度为 O ( ∣ V ∣ 2 ) O(|V|^2) O(V2)

DFS 实现拓扑排序的思想

  • 利用深度优先遍历也可以实现拓扑排序。
  • 对于有向无环图 G G G 中的任意结点 u , v u, v u,v,它们之间的关系必然是下列三种之一:
    1. u u u v v v 的祖先,则在调用 DFS 访问 u u u 之前,必然已对 v v v 进行了 DFS 访问,即 v v v 的 DFS 结束时间先于 u u u 的 DFS 结束时间。从而可考虑在 DFS 函数中设置一个时间标记,在 DFS 调用结束时,对各顶点计时。因此,祖先的结束时间必然大于子孙的结束时间。
    2. u u u v v v 的子孙,则 v v v u u u 的祖先,按上述思路, v v v 的结束时间大于 u u u 的结束时间。
    3. u u u v v v 没有路径关系,则 u u u v v v 在拓扑序列的关系任意。

于是,按结束时间从大到小排列,就可以得到一个拓扑排序序列。

逆拓扑排序

定义

逆拓扑排序是另一种针对有向无环图(AOV网)的一种排序方法,其步骤如下:

  1. 选择顶点:从 AOV 网中选择一个没有后继(出度为 0)的顶点并输出。
  2. 删除顶点:从网中删除该顶点和所有以它为终点的有向边。
  3. 重复操作:重复步骤 1 和 2 直到当前的 AOV 网为空。
应用

逆拓扑排序常用于确定任务的执行顺序,特别是在项目管理和编译原理中,用于确保任务按照依赖关系正确执行。

注意事项

用拓扑排序算法处理 AOV 网时,应注意以下问题:

  1. 入度为零的顶点,即没有前驱活动的或前驱活动都已经完成的顶点,工程可以从这个顶点所代表的活动开始或继续。
  2. 若一个顶点有多个直接后继,则拓扑排序的结果通常不唯一;但若各个顶点已经排在一个线性有序的序列中,每个顶点有唯一的前驱后继关系,则拓扑排序的结果是唯一的。
  3. 由于 AOV 网中各顶点的地位平等,每个顶点编号是人为的,因此可以按拓扑排序的结果重新编号,生成 AOV 网的新的邻接存储矩阵,这种邻接矩阵可以是三角矩阵;但对于一般的图来说,若其邻接矩阵是三角矩阵,则存在拓扑序列;反之则不一定成立。

关键路径

关键路径是指在项目管理中,从项目开始到项目结束的最长路径。关键路径上的活动决定了项目的最短完成时间,对项目管理和进度控制有重要意义。
在AOE网中,有些活动是可以并行进行的。从源点到汇点的有向路径可能有多条,并且这些路径长度可能不同。完成不同路径上的活动所需的时间虽然不同,但是只有所有路径上的活动都已完成,整个工程才能算结束。因此,从源点到汇点的所有路径中,具有最大路径长度的路径称为关键路径,而把关键路径上的活动称为关键活动。

  • 关键路径的影响
    完成整个工程的最短时间就是关键路径的长度,即关键路径上各活动花费开销的总和。这是因为关键活动影响了整个工程的时间,即若关键活动不能按时完成,则整个工程的完成时间就会延长。因此,只要找到了关键活动,就找到了关键路径,也就可以得出最短完成时间。

AOE网与AOV网

  • AOV网:AOV网中的边无权值,仅表示顶点之间的前后关系。
  • AOE网:在带权有向图中,以顶点表示事件,以有向边表示活动,以边上的权值表示完成该活动的开销(如完成活动所需的时间),称之为用边表示活动的网络,简称AOE网。

AOE 网和 AOV 网都是有向无环图,不同之处在于它们的边和顶点所代表的含义是不同的,AOE 网中的边有权值,而 AOV网中的边无权值。

AOE网的性质

AOE网具有以下两个性质:

  1. 只有在某顶点所代表的事件发生后,从该顶点出发的各有向边所代表的活动才能开始。
  2. 只有在进入某顶点的各向边所代表的活动都已结束时,该顶点所代表的事件才能发生。

在 AOE 网中仅有一个入度为 0 的顶点,称为开始顶点(源点),它表示整个工程的开始;也仅有一个出度为 0 的顶点,称为结束顶点(汇点),它表示整个工程的结束。

关键路径分析参数

事件的最早发生时间 v e ( k ) v_e(k) ve(k)
定义

事件 v k v_k vk 的最早发生时间是指从源点 v 1 v_1 v1 到顶点 v k v_k vk 的最长路径长度。它决定了所有从 v k v_k vk 开始的活动能够开工的最早时间。

计算公式
  • v e ( 源点 ) = 0 v_e(\text{源点}) = 0 ve(源点)=0
  • v e ( k ) = max ⁡ { v e ( j ) + Weight ( v j , v k ) } , v k 为  v j 的任意后继 v_e(k) = \max\{v_e(j) + \text{Weight}(v_j, v_k)\}, v_k \text{ 为 } v_j \text{ 的任意后继} ve(k)=max{ve(j)+Weight(vj,vk)},vk  vj 的任意后继 Weight ( v j , v k ) \text{Weight}(v_j, v_k) Weight(vj,vk) 表示 < v j , v k > <v_j, v_k> <vj,vk> 上的权值。
注意事项

计算 v e ( ) v_e() ve() 值时,按从前往后的顺序进行,可以在拓扑排序的基础上计算:

  1. 初始时,令 v e [ 1... n ] = 0 v_e[1...n] = 0 ve[1...n]=0
  2. 输出一个入度为 0 的顶点 v j v_j vj 时,计算它所有直接后继顶点 v k v_k vk 的最早发生时间,若 v e [ j ] + Weight ( v j , v k ) > v e [ k ] v_e[j] + \text{Weight}(v_j, v_k) > v_e[k] ve[j]+Weight(vj,vk)>ve[k],则 v e [ k ] = v e [ j ] + Weight ( v j , v k ) v_e[k] = v_e[j] + \text{Weight}(v_j, v_k) ve[k]=ve[j]+Weight(vj,vk)。以此类推,直至输出全部顶点。
事件的最迟发生时间 v l ( k ) v_l(k) vl(k)
定义

事件 v k v_k vk 的最迟发生时间是指在不推迟整个工程完成的前提下,即保证它的后继事件 v j v_j vj 在其最迟发生时间 v l ( j ) v_l(j) vl(j) 能够发生时,该事件最迟必须发生的时间。

计算公式
  • v l ( 汇点 ) = v e ( 汇点 ) v_l(\text{汇点}) = v_e(\text{汇点}) vl(汇点)=ve(汇点)
  • v l ( k ) = min ⁡ { v l ( j ) − Weight ( v k , v j ) } , v k 为  v j 的任意前驱 v_l(k) = \min\{v_l(j) - \text{Weight}(v_k, v_j)\}, v_k \text{ 为 } v_j \text{ 的任意前驱} vl(k)=min{vl(j)Weight(vk,vj)},vk  vj 的任意前驱
注意事项

计算 v l ( k ) v_l(k) vl(k) 值时,按从后往前的顺序进行,可以在逆拓扑排序的基础上计算。增设一个栈以记录拓扑序列,拓扑排序结束后从栈底至栈顶便为逆拓扑有序序列。过程如下:

  1. 初始时,令 v l [ 1... n ] = v e [ n ] v_l[1...n] = v_e[n] vl[1...n]=ve[n]
  2. 栈顶顶点 v j v_j vj 出栈,计算其所有直接前驱顶点 v k v_k vk 的最迟发生时间,若 v l [ j ] − Weight ( v k , v j ) < v l [ k ] v_l[j] - \text{Weight}(v_k, v_j) < v_l[k] vl[j]Weight(vk,vj)<vl[k],则 v l [ k ] = v l [ j ] − Weight ( v k , v j ) v_l[k] = v_l[j] - \text{Weight}(v_k, v_j) vl[k]=vl[j]Weight(vk,vj)。以此类推,直至输出全部栈中顶点。
活动 a i a_i ai 的最早开始时间 e ( i ) e(i) e(i)
  • 定义:它是指该活动弧的起点所表示的事件的最早发生时间。若边 < v k , v j > <v_k, v_j> <vk,vj> 表示活动 a i a_i ai,则有 e ( i ) = v e ( k ) e(i) = v_e(k) e(i)=ve(k)
活动 a i a_i ai 的最迟开始时间 l ( i ) l(i) l(i)
  • 定义:它是指该活动弧的终点所表示事件的最迟发生时间与该活动所需时间之差。若边 < v k , v j > <v_k, v_j> <vk,vj> 表示活动 a i a_i ai,则有 l ( i ) = v l ( j ) − Weight ( v k , v j ) l(i) = v_l(j) - \text{Weight}(v_k, v_j) l(i)=vl(j)Weight(vk,vj)
活动 a i a_i ai 的时间余量 d ( i ) d(i) d(i)
  • 定义:一个活动 a i a_i ai 的最迟开始时间 l ( i ) l(i) l(i) 和其最早开始时间 e ( i ) e(i) e(i) 的差额 d ( i ) = l ( i ) − e ( i ) d(i) = l(i) - e(i) d(i)=l(i)e(i)
  • 意义:它是指该活动完成的时间余量,即在不增加完成整个工程所需总时间的情况下,活动 a i a_i ai 可以拖延的时间。
  • 关键活动:若一个活动的时间余量为零,则说明该活动必须要如期完成,否则就会拖延整个工程的进度,所以称 l ( i ) − e ( i ) = 0 l(i) - e(i) = 0 l(i)e(i)=0 l ( i ) = e ( i ) l(i) = e(i) l(i)=e(i) 的活动 a i a_i ai 是关键活动。

关键路径的实例

求关键路径的算法步骤
  1. 从源点出发,令 v e ( 源点 ) = 0 v_e(\text{源点}) = 0 ve(源点)=0,按拓扑有序求其余顶点的最早发生时间 v e ( ) v_e() ve()
  2. 从汇点出发,令 v l ( 汇点 ) = v e ( 汇点 ) v_l(\text{汇点}) = v_e(\text{汇点}) vl(汇点)=ve(汇点),按逆拓扑有序求其余顶点的最迟发生时间 v l ( ) v_l() vl()
  3. 根据各顶点的 v e ( ) v_e() ve() 值求所有弧的最早开始时间 e ( ) e() e()
  4. 根据各顶点的 v l ( ) v_l() vl() 值求所有弧的最迟开始时间 l ( ) l() l()
  5. 求 AOE 网中所有活动的差额 d ( ) d() d(),找出所有 d ( ) = 0 d() = 0 d()=0 的活动构成关键路径。
实例分析

下图所示为求解关键路径的过程,简单说明如下:

  1. v e ( ) v_e() ve():初始 v e ( 1 ) = 0 v_e(1) = 0 ve(1)=0,在拓扑排序输出顶点过程中,求得 v e ( 2 ) = 3 v_e(2) = 3 ve(2)=3 v e ( 3 ) = 2 v_e(3) = 2 ve(3)=2 v e ( 4 ) = max ⁡ { v e ( 2 ) + 2 , v e ( 3 ) + 4 } = max ⁡ { 5 , 6 } = 6 v_e(4) = \max\{v_e(2) + 2, v_e(3) + 4\} = \max\{5, 6\} = 6 ve(4)=max{ve(2)+2,ve(3)+4}=max{5,6}=6 v e ( 5 ) = 6 v_e(5) = 6 ve(5)=6 v e ( 6 ) = max ⁡ { v e ( 5 ) + 1 , v e ( 4 ) + 2 , v e ( 3 ) + 3 } = max ⁡ { 7 , 8 , 5 } = 8 v_e(6) = \max\{v_e(5) + 1, v_e(4) + 2, v_e(3) + 3\} = \max\{7, 8, 5\} = 8 ve(6)=max{ve(5)+1,ve(4)+2,ve(3)+3}=max{7,8,5}=8
  2. v l ( ) v_l() vl():初始 v l ( 6 ) = 8 v_l(6) = 8 vl(6)=8,在逆拓扑排序出栈过程中,求得 v l ( 5 ) = 7 v_l(5) = 7 vl(5)=7 v l ( 4 ) = 6 v_l(4) = 6 vl(4)=6 v l ( 3 ) = min ⁡ { v l ( 4 ) − 4 , v l ( 6 ) − 3 } = min ⁡ { 2 , 5 } = 2 v_l(3) = \min\{v_l(4) - 4, v_l(6) - 3\} = \min\{2, 5\} = 2 vl(3)=min{vl(4)4,vl(6)3}=min{2,5}=2 v l ( 2 ) = min ⁡ { v l ( 5 ) − 3 , v l ( 4 ) − 2 } = min ⁡ { 4 , 4 } = 4 v_l(2) = \min\{v_l(5) - 3, v_l(4) - 2\} = \min\{4, 4\} = 4 vl(2)=min{vl(5)3,vl(4)2}=min{4,4}=4 v l ( 1 ) v_l(1) vl(1) 必然为 0 而无须再求。
  3. 弧的最早开始时间 e ( i ) e(i) e(i) 等于该弧的起点的顶点的 v e ( ) v_e() ve(),结果如下表。
  4. 弧的最迟开始时间 l ( i ) l(i) l(i) 等于该弧的终点的顶点的 v l ( ) v_l() vl() 减去该弧持续的时间,结果如下表。
  5. 根据 l ( i ) − e ( i ) = 0 l(i) - e(i) = 0 l(i)e(i)=0 的关键活动,得到的关键路径为 ( v 1 , v 3 , v 4 , v 6 ) (v_1, v_3, v_4, v_6) (v1,v3,v4,v6)

在这里插入图片描述

缩短工期的相关分析

对于关键路径,需要注意以下几点:

  1. 关键路径上的所有活动都是关键活动,它是决定整个工程的关键因素,因此可以通过加快关键活动来缩短整个工程的工期。但也不能任意缩短关键活动,因为一旦缩短到一定程度,该关键活动就可能会变成非关键活动。
  2. 网中的关键路径并不唯一,且对于有几条关键路径的网,只提高一条关键路径上的关键活动速度并不能缩短整个工程的工期,只有加快那些包括在所有关键路径上的关键活动才能达到缩短工期的目的。

不同存储结构时各种图算法的时间复杂度

算法邻接矩阵邻接表
Dijkstra O ( n 2 ) O(n^2) O(n2)-
Floyd O ( n 3 ) O(n^3) O(n3)-
Prim O ( n 2 ) O(n^2) O(n2)-
Kruskal- O ( e log ⁡ e ) O(e \log e) O(eloge)
DFS O ( n 2 ) O(n^2) O(n2) O ( n + e ) O(n + e) O(n+e)
BFS O ( n 2 ) O(n^2) O(n2) O ( n + e ) O(n + e) O(n+e)
拓扑排序 O ( n 2 ) O(n^2) O(n2) O ( n + e ) O(n + e) O(n+e)
关键路径 O ( n 2 ) O(n^2) O(n2) O ( n + e ) O(n + e) O(n+e)

六、参考资料

鲍鱼科技课件

b站免费王道课后题讲解:
在这里插入图片描述

网课全程班:
在这里插入图片描述

26王道考研书

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/41563.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Photoshop 2025安装包下载及Photoshop 2025详细图文安装教程

文章目录 前言一、Photoshop 2025安装包下载二、Photoshop 2025安装教程1.解压安装包2.运行程序3.修改安装路径4.设安装目录5.开始安装6.等安装完成7.关闭安装向导8.启动软件9.安装完成 前言 无论你是专业设计师&#xff0c;还是初涉图像处理的小白&#xff0c;Photoshop 2025…

MySQL-存储过程

介绍 基本语法 创建 调用 查看 删除 变量 系统变量 查看 设置 用户定义变量 赋值 使用 局部变量 声明 赋值 流程控制 参数 条件结构 IF case 循环结构 while repeat loop 游标 条件处理程序 介绍 举个简单的例子&#xff0c;我们先select某数据&…

debug 笔记:llama 3.2 部署bug 之cutlassF: no kernel found to launch!

1 问题描述 按照官方的写法 import torch from transformers import pipeline import os os.environ["HF_TOKEN"] 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 model_id "meta-llama/Llama-3.2-3B"pipe pipeline("text-generation", modelmode…

《Python实战进阶》No34:卷积神经网络(CNN)图像分类实战

第34集&#xff1a;卷积神经网络&#xff08;CNN&#xff09;图像分类实战 摘要 卷积神经网络&#xff08;CNN&#xff09;是计算机视觉领域的核心技术&#xff0c;特别擅长处理图像分类任务。本集将深入讲解 CNN 的核心组件&#xff08;卷积层、池化层、全连接层&#xff09;…

【银河麒麟系统常识】命令:uname -m(查看系统架构)

命令&#xff1a; uname -m 功能 常用的 Linux/Unix 终端命令&#xff0c;用于显示当前系统的硬件架构&#xff1b; 返回 返回系统的CPU架构类型&#xff0c;用于判断软件兼容性&#xff1b; 输出结果架构说明常见设备x86_64Intel/AMD 64位 CPU主流 PC、服务器aarch64ARM 64位 …

游戏引擎学习第183天

回顾和今天的计划 我对接下来的进展感到非常兴奋。虽然我们可能会遇到一些问题&#xff0c;但昨天我们差不多完成了将所有内容迁移到新的日志系统的工作&#xff0c;我们正在把一些内容整合进来&#xff0c;甚至是之前通过不同方式记录时间戳的旧平台层部分&#xff0c;现在也…

Redisson 实现分布式锁简单解析

目录 Redisson 实现分布式锁业务方法&#xff1a;加锁逻辑LockUtil 工具类锁余额方法&#xff1a;工具类代码枚举代码 RedisUtil 工具类tryLock 方法及重载【分布式锁具体实现】Supplier 函数式接口调用分析 Redisson 实现分布式锁 业务方法&#xff1a; 如图&#xff0c;简单…

鸿蒙Flutter实战:19-Flutter集成高德地图,跳转页面方式

前言 在之前的文章现有Flutter项目支持鸿蒙II中&#xff0c;介绍了如何使用第三方插件&#xff0c;同时给出了非常多的使用案例&#xff0c;如 flutter_inappwebview&#xff0c;video_player, image_picker 等&#xff0c;本文将开始介绍如何集成高德地图。 整体方案 通过 …

26考研——图_图的代码实操(6)

408答疑 文章目录 五、图的代码实操图的存储邻接矩阵结构定义初始化插入顶点获取顶点位置在顶点 v1 和 v2 之间插入边获取第一个邻接顶点获取下一个邻接顶点显示图 邻接表结构定义初始化图插入顶点获取顶点位置在顶点 v1 和 v2 之间插入边获取第一个邻接顶点获取下一个邻接顶点…

力扣32.最长有效括号(栈)

32. 最长有效括号 - 力扣&#xff08;LeetCode&#xff09; 代码区&#xff1a; #include<stack> #include<string> /*最长有效*/ class Solution { public:int longestValidParentheses(string s) {stack<int> st;int ans0;int ns.length();st.push(-1);fo…

Node.js 下载安装及环境配置教程、卸载删除环境配置超详细步骤(附图文讲解!) 从零基础入门到精通,看完这一篇就够了

Node.js 安装 一、进入官网地址下载安装包 Node.js — Download Node.js 选择对应你系统的Node.js版本&#xff0c;这里我选择的是Windows系统、64位 Tips&#xff1a;如果想下载指定版本&#xff0c;点击【以往的版本】&#xff0c;即可选择自己想要的版本下载 二、安装程序…

SQLark导出功能详解|轻松管理数据库数据与结构

SQLark 作为一款数据库管理工具&#xff0c;为用户提供了丰富且实用的导出功能。在数据库管理与开发过程中&#xff0c;数据及结构的导出操作至关重要&#xff0c;关乎数据的迁移、备份、版本管理以及问题定位等诸多关键环节。接下来&#xff0c;让我们深入了解 SQLark 的导出功…

搭建Redis主从集群

主从集群说明 单节点Redis的并发能力是有上限的&#xff0c;要进一步提高Redis的并发能力&#xff0c;就需要搭建主从集群&#xff0c;实现读写分离。 主从结构 这是一个简单的Redis主从集群结构 集群中有一个master节点、两个slave节点&#xff08;现在叫replica&#xff09;…

自然语言处理(NLP)技术的应用面有哪些

自然语言处理&#xff08;NLP&#xff09;技术在各个领域都有广泛的应用&#xff0c;以下是一些常见的例子&#xff1a; 机器翻译&#xff1a;NLP技术用于开发翻译系统&#xff0c;可以将一个语言的文本自动翻译成另一种语言。例如&#xff0c;谷歌翻译就是一个应用了NLP技术的…

element-plus 的简单应用

前言 本篇博客是 基于 ElementPlus 快速入门_element plus x-CSDN博客 的进阶 最终成果 完成的要求 1 深入学习 设计 | Element Plus 从里面找自己合适的 使用到的 组件有&#xff1a;表格&#xff0c;分页条&#xff0c;表单&#xff0c;卡片 2 具备 前端基础&#xff08;ht…

关于Qt的各类问题

目录 1、问题&#xff1a;Qt中文乱码 2、问题&#xff1a;启动时避免ComBox控件出现默认值 博客会不定期的更新各种Qt开发的Bug与解决方法,敬请关注! 1、问题&#xff1a;Qt中文乱码 问题描述&#xff1a;我在设置标题时出现了中文乱码 this->setWindowTitle("算法…

海思烧录工具HITool电视盒子刷机详解

HiTool是华为开发的一款用于海思芯片设备的刷机和调试工具&#xff0c;可对搭载海思芯片的机顶盒、智能电视等设备进行固件烧录、参数配置等操作。以下为你详细介绍&#xff1a; 功能用途 固件烧录&#xff1a;这是HiTool最主要的功能之一。它能够将下载好的适配固件文件烧录到…

Docker Compose介绍

基本概念 Docker-Compose是Docker官方的开源项目&#xff0c;负责实现对docker容器集群的快速编排。 可以这么理解&#xff0c;docker compose是docker提出的一个工具软件&#xff0c;可以管理多个docker容器组成一个应用&#xff0c;只需要编写一个YAML格式的配置文件docker…

大疆上云api直播功能如何实现

概述 流媒体服务器作为直播画面的中转站,它接收推流端的相机画面,同时拉流端找它获取相机的画面。整个流程如下: 在流媒体服务器上创建流媒体应用(app),一个流媒体服务器上面可以创建多个流媒体应用约定推拉流的地址。假设流媒体服务器工作在1935端口上面,假设创建的流…

LabVIEW远程控制通讯接口

abVIEW提供了多种远程控制与通讯接口&#xff0c;适用于不同场景下的设备交互、数据传输和系统集成。这些接口涵盖从基础的网络协议&#xff08;如TCP/IP、UDP&#xff09;到专用技术&#xff08;如DataSocket、远程面板&#xff09;&#xff0c;以及工业标准协议&#xff08;如…