算法导论 总结索引 | 第五部分 第二十四章:单源最短路径

1、在最短路径问题中,给定一个带权重的有向图 G = (V, E) 和权重函数 w: E→R ,该权重函数 将每条边映射到实数值的权重上。图中一条路径 p =〈v0, v1, …, vk〉 的权重 w(p) 是构成该路径的 所有边的权重之和:
在这里插入图片描述
定义从结点 u 到结点 v 的最短路径权重 δ(u, v) 如下:
在这里插入图片描述
从结点 u 到结点 v 的最短路径则定义为 任何一条权重为 w§ = δ(u, v) 从 u 到 v 的路径 p

2、22.2 节讨论的广度优先搜索算法 就是一个求取最短路径的算法,但该算法 只能用于无权图,即每条边的权重 都是单位权重的图

3、最短路径的几个变体
讨论 单源最短路径问题:给定一个图 G = (V, E),希望找到从给定源结点 s ∈ V 到每个结点 v ∈ V 的最短路径

单目的地 最短路径问题:找到从每个结点 v 到给定目的地结点 t 的最短路径。如果 将图的每条边的方向翻转过来,就可将这个题转换为单源最短路径问题

单节点对最短路径问题:找到从给定结点 u 到给定结点 v 的最短路径。如果 解决了针对单个结点 u 的单源最短路径问题,那么也就解决了这个问题。而且,在该问题的所有已知算法中,最坏情况下的渐近运行时间 都和最好的单源最短路径算法的运行时间一样

所有节点对 最短路径问题:对于每对结点 u 和 v,找到从结点 u 到结点 v 的最短路径。虽然 可以针对每个结点运行一遍单源最短路径算法,但通常可以更快地解决这个问题

4、最短路径的最优子结构
最短路径算法 通常依赖于最短路径的一个重要性质:两个结点之间的一条最短路径 包含着其他的最短路径。最优子结构 是可以使用动态规划(第 15 章)和贪心算法(第 16 章)的一个重要指标。Dijkstra 算法就是一个贪心算法,而 Floyd-Warshall 算法则是一个动态规划算法,该算法 能够找出所有节点对之间的最短路径

引理 24.1 (最短路径的子路径也是最短路径)
给定带权重的有向图 G = (V, E) 和权重函数 w: E->R。设 p = <v_0, v_1, …, v_k> 为从起点 v_0 到终点 v_k 的一条最短路径,并且对于任意的 i 和 j,0 <= i <= j <= k,设 p_ij = <v_i, v_{i+1}, …, v_j> 为路径 p 中从节点 v_i 到节点 v_j 的子路径。那么 p_ij 是从节点 v_i 到节点 v_j 的一条最短路径

5、如果图 G 包括从 s 可以达到的权重为负值的环路(可达+环路权重为负),则最短路径权重无定义。从 s 到该环路上的任意结点的路径 都不可能是最短路径,因为 可以沿着任何“最短”路径 再经历一次权重为负值的环路,则总是可以找到一条权重更小的路径

因为环路 <e, f, e> 的权重为 3 + (−6) = −3 < 0,从结点 s 到结点 e 没有最短路径。通过经历 负权重环路 <e, f, e> 任意次数,可以找到 权重为任意负值的 从结点 s 到结点 e 的路径,因此 δ(s, e) = −∞。类似地,δ(s, f) = −∞。因为结点 g 可以从结点 f 到达,可以找到 一条权重为任意负值的 从结点 s 到结点 g 的路径,因此 δ(s, g) = −∞。结点 h、i 和 j 也形成一个权重为负值的环路,但它们不能从结点 s 到达,因此 δ(s, h) = δ(s, i) = δ(s, j) = ∞
在这里插入图片描述
某些最短路径算法(如Dijkstra算法)假设输入图的所有边权重为 非负值。另外一些算法(如Bellman-Ford算法),允许输入图中包含 负权重的边。但只要 没有可以 从源结点到达的权重为负值的环路,就可以生成正确的答案。在通常情况下,如果存在一条权重为负值的环路,Bellman-Ford 算法可以侦测并报告其存在

6、环路
最短路径 也不能包含权重为正值的环路,因为 只要将环路从路径上删除 就可以得到一条源结点和终结点与原来路径相同的 一条权重更小的路径

只要一条最短路径上 还有权重为 0 的环路,就可以 重复删除这些环路,直到得到 一条不包括环路的最短路径。因此,不失一般性,可以假定 在找到的最短路径中没有环路,即它们都是简单路径。由于图 G = (V,E) 中的任意无环路径 最多包含 |V| 个不同的结点,它也最多包含 |V| - 1 条边。因此,可以将注意力集中在 至多只包含 |V| - 1 条边的最短路径上

7、最短路径的表示
不但希望 计算出最短路径权重,还希望 计算出最短路径上的结点。对最短路径的表示 与 22.2节中对广度优先搜索树的表示相似。给定图 G = (V,E),对于每个结点 v,维持一个前驱结点 v.π。该前驱结点 可能是另一个结点 或者 NIL。本章的最短路径算发将对每个结点的 π 属性进行设置,这样,将从结点 v 开始的前驱结点链反转过来,就是从 s 到 v 的一条最短路径

在运行最短路径算法的过程中,π 值并不一定能给出最短路径(算法尚未完成、存在负权重环 或 存在多条最短路径的情况)

由 π 值所诱导的前驱子图 Gπ = (Vπ, Eπ)。定义结点集 Vπ 为图 G 中的前驱结点不是 NIL 的结点的集合,再加上源结点 s,即
在这里插入图片描述
有向边集合 Eπ 是由 Vπ 中的结点的 π 值所诱导的边的集合,即
在这里插入图片描述
在算法终止时,Gπ 是一棵最短路径树。非形式化地说,最短路径树 是一棵有根结点的树,该树包括了 从源结点 s 到每个可以从 s 到达的结点的一条最短路径。一棵最短路径树 有点类似于 22.2 节中的广度优先树,但它所包括的最短路径 是以边的权重来定义的,而不是 边的条数
更准确地说,设 G = (V, E) 是一棵带权重的有向图,其权重函数为 ω:E→R,假定 G 不包含从 s 可以到达的权重为负值的环路,因此,所有的最短路径都有定义。一棵根结点为 s 的最短路径树 是一个有向子图 G′ = (V′, E′),这里V′⊆V,E′⊆E,满足:

  1. V′ 是图 G 中从源结点 s 可以到达的所有结点的集合
  2. G′ 形成一棵根结点为 s 的树
  3. 对于所有的结点 v ∈ V′,图 G′ 中从结点 s 到结点 v 的唯一简单路径是 图 G 中从结点 s 到结点 v 的一条最短路径

最短路径不一定是唯一的,最短路径树也不一定唯一
在这里插入图片描述
8、松弛操作
对于每个结点 v 来说,维持一个属性 v.d,用来记录从源结点 s 到结点 v 的最短路径 权重的上界。称 v.d 为 s 到 v 的最短路径估计。使用下面运行时间为 Θ(V) 的算法 来对最短路径估计和前驱结点进行初始化:

INITIALIZE-SINGLE-SOURCE(G,s)
1 for each vertex v in G.V
2 	v.d =3 	v.π = NIL
4 s.d = 0

对一条边 (u,v) 的松弛过程为:首先测试一下 是否可以对 s 到 v 的最短路径进行改善

测试的方法是,将从结点 s 到结点 u 之间的最短路径距离 加上结点 u 与 v 之间的边权重,并与当前的 s 到 v 的最短路径估计进行比较,如果前者更小,则对 v.d 和 v.π 进行更新。松弛步骤 可能降低最短路径的估计值 v.d 并更新 v 的前驱属性 v.π
对边 (u,v) 在 O(1) 时间内进行的松弛操作 :

RELAX(u,v,w)
1 if v.d > u.d + w(u,v)
2 	v.d = u.d + w(u,v)
3 	v.π = u

在这里插入图片描述
本章的每个算法 都将调用算法 INITIALIZE-SINGLE-SOURCE,然后重复对边进行松弛。而且,松弛 是唯一导致最短路径估计和前驱结点发生变化的操作。Dijkstra 算法 和 用于有向无环图的最短路径算法 对每条边仅松弛一次。Bellman-Ford 算法则对每条边松弛 |V| - 1 次

9、最短路径 和 松弛操作的性质
成立的前提是 必须调用 INITIALIZE-SINGLE-SOURCE(G, s) 来对图进行初始化,并且 所有对最短路径估计 和 前驱子图所做的改变 都是通过一系列的松弛步骤 来实现的

三角不等式性质 (引理 24.10) 对于任何边 (u, v) ∈ E,我们有 δ(s, v) ≤ δ(s, u) + w(u, v)

上界性质 (引理 24.11) 对于所有的结点 v ∈ V,总是有 v.d ≥ δ(s, v)。一旦 v.d 的取值达到 δ(s, v),它的值将不再发生变化

非路径性质 (推论 24.12) 如果从结点 s 到结点 v 之间不存在路径,则总是有 v.d = δ(s, v) = ∞

收敛性质 (引理 24.14) 对于某些结点 u, v ∈ V,如果 s → u → v 是图 G 中的一条最短路径,并且在对边 (u, v) 进行松弛前的任意时间有 u.d = δ(s, u),则在之后的所有时间有 v.d = δ(s, v)

路径松弛性质 (引理 24.15) 如果 p = <v0, v1, …, vk> 是从源结点 s = v0 到结点 vk 的一条最短路径,并且 对 p 中的边所进行松弛的次序为 (v0, v1), (v1, v2), …, (vk-1, vk),则 vk.d = δ(s, v_k)。该性质的建立 与任何其他的松弛操作无关,即使这些松弛操作是与对 p 上的边所进行的松弛操作穿插进行的

前驱子图性质 (引理 24.17) 对于所有的结点 v ∈ V,一旦 v.d = δ(s, v),则前驱子图是 一棵根结点为 s 的最短路径树

所有算法 都假设有向图 G 以邻接链表的方式予以存放。此外,边的权重与边本身存放在一起,这样在遍历每条邻接链表时,可以在 O(1) 时间内获得边的权重

1、Bellman-Ford 算法

1、Bellman-Ford 算法 解决的是一般情况下的单源最短路径问题,在这里,边的权重可以为负值。给定带权重的有向图 G = (V, E) 和权重函数 w: E→R,Bellman-Ford 算法返回一个布尔值,以表明 是否存在一个从源结点可以到达的权重为负值的环路。如果存在这样一个环路,算法将告诉我们 不存在解决方案。如果没有这种环路存在,算法将给出最短路径和它们的权重

Bellman-Ford 算法 通过对边进行松弛操作 来逐渐地降低从源结点 s 到每个结点 v 的最短路径的估计值 v.d,直到该估计值与实际的最短路径权重 δ(s, v) 相同时为止。该算法返回 TRUE 值 当且仅当输入图不包含可以从源结点到达的权重为负值的环路
在这里插入图片描述

BELLMAN-FORD(G,w,s)
1 INITIALIZE-SINGLE-SOURCE(G,s)
2 for i to |G.V|-1
3 	for each edge(u,v)in G.E
4 		RELAX(u,v,w)
5 for each edge(u,v)in G.E
6 	if v.d > u.d+w(u,v)
7 		return FALSE
8 return TRUE

每一次处理对应的是 算法第 2~4 行 for 循环的一次循环,该循环对图的 每条边 进行一次松弛操作。在进行了 |V| - 1 次松弛操作后,算法第 5~8 行负责 检查图中是否存在权重为负值的环路 并返回与之相适应的布尔值
在这里插入图片描述
Bellman-Ford 算法的总运行时间为 O(VE)

2、要证明 Bellman-Ford 算法的正确性,首先证明 在没有权重为负值的环路的情况下,该算法 正确计算出从源结点可以到达的所有结点之间的最短路径权值

引理 24.2 设 G = (V, E) 为一个带权重的源结点为 s 的有向图,其权重函数为 w:E→R。假定图 G 不包含从源结点 s 可以到达的权重为负值的环路。那么在算法 BELLMAN-FORD 的第 2~4 行的 for 循环执行了 |V| -1 次之后,对于所有从源结点 s 可以到达的结点 v,我们有 v.d = δ(s, v)

证明:通过使用路径松弛性质来证明本引理。考虑任意从源结点 s 可以到达的结点 v,设 p = <v0, v1, …, vk> 为从源结点 s 到结点 v 之间的任意一条最短路径,这里 v0=s,vk=v。因为最短路径都是简单路径,p 最多包含 |V| -1 条边,因此 k ≤ |V| -1。算法第 2~4 行的 for 循环 每次松弛所有的 |E| 条边。在第 i 次松弛操作时,这里 i=1, 2,…, k,被松弛的边中包含 (vi-1, vi)(路径上的边 按照顺序逐步被松弛)

在第一次迭代时,路径上的第一条边 (v0, v1) 被松弛,这将更新 v1 的最短路径估计值,使其等于从 s 到 v1 的最短路径权重
在第二次迭代时,路径上的第二条边 (v1, v2) 被松弛,更新 v2 的最短路径估计值为从 s 到 v2 的最短路径权重
依此类推,在第 k 次迭代时,路径上的边 (v_k−1, vk) 被松弛,最终更新 vk 的最短路径估计值 k.d 为 δ(s, vk),也就是从源点 s 到终点 v 的最短路径权重

根据路径松弛性质,v.d = vk.d = δ(s, vk) = δ(s, v)

3、推论 24.3 设 G = (V, E) 是一带权重的源结点为 s 的有向图,其权重函数为 w: E→R。假定图 G 不包含从源结点 s 可以到达的权重为负值的环路,则对于所有结点 v∈V,存在一条从源结点 s 到结点 v 的路径当且仅当 BELLMAN-FORD 算法终止时 v.d < ∞
在这里插入图片描述
在这里插入图片描述
4、定理 24.4 (Bellman-Ford 算法的正确性)
设 BELLMAN-FORD 算法运行在一帯权重的源结点为 s 的有向 G = (V, E) 上,该图的权重函数为 w: E→R。如果图 G 不包含从源结点 s 可以到达的权重为负值的环路,则算法将返回 TRUE 值,并且对于所有结点 v∈V,前驱子图 Gπ 是一棵根结点为 s 的最短路径树。如果图 G 包含一条从源结点 s 可以到达的权重为负值的环路,则算法将返回 FALSE 值

证明:
首先证明,对于所有结点 v ∈ V,在算法 BELLMAN-FORD 终止时,有 v.d = δ(s, v)。如果结点 v 是从 s 可以到达的,则引理 24.2 证明了本论断。如果结点 v 不能从 s 到达,则该论断 可以从非路径性质获得。因此,该论断得到证明
在这里插入图片描述

综合前驱子图性质 和 本论断 可以推导出 Gπ 是一棵最短路径树。现在,我们使用这个论断来证明 BELLMAN-FORD 算法返回的值 确实判断了 有没有 源点可达的负环路
在算法 BELLMAN-FORD 终止时,对于所有的边 (u, v) ∈ E
在这里插入图片描述
因此,它一定返回的是 TRUE 值

现在,假定图 G 包含一个权重为负值的环路,并且该环路可以从源结点 s 到达
在这里插入图片描述
使用反证法。假设 Bellman-Ford 算法返回的是 TRUE 值,则 vi.d ≤ v_i-1.d + w(v_i-1, vi),这里 i=1, 2,…,k。将环路 c 上的所有这种不等式加起来,有
在这里插入图片描述
由于 v0=vk,环路 c 上的每个结点在上述求和表达式 ∑vi.d 和 ∑vi-1.d 中刚好各出现一次,因此有
在这里插入图片描述
根据推论 24.3,vi.d 对于 i=1, 2,…,k 来取的都是有限值,因此有
在这里插入图片描述
与条件
在这里插入图片描述
矛盾,因此,得出结论,如果图 G 不包含从源结点 s 可以到达的权重为负值的环路,则 Bellman-Ford 算法返回 TRUE 值,否则返回 FALSE 值

24.1-3 给定 G = (V, E) 是一带权重 且没有权重为负值的环路的有向图,对于所有结点 v∈V,从源结点 s 到结点 v 之间的最短路径中,包含边的最大条数为 m。(这里,判断最短路径的依据是权重,不是边的条数)请对算法 BELLMAN-FORD 进行简单修改,可以让他在 m+1 遍松弛操作之后终止,即使 m 不是一个事先知道的一个数值

提前终止条件:在 Bellman-Ford 算法的每次迭代中,如果在一次完整的松弛过程中,所有的边 (u, v) 都没有引起最短路径估计值
v.d 的变化(即不再有松弛操作能更新任何结点的最短路径),那么可以认为最短路径 已经确定,此时算法可以提前终止,而无需再继续剩余的松弛操作

def Bellman_Ford_early_terminate(G, w, s):
    # 初始化距离和前驱
    Initialize-Single-Source(G, s)
    
    # 对所有节点最多进行 |V| - 1 次松弛操作
    for i from 1 to |V| - 1:
        # 标记这次迭代是否有任何更新发生
        updated = False
        
        # 对每条边 (u, v) 进行松弛操作
        for each edge (u, v) in E[G]:
            if Relax(u, v, w):
                updated = True  # 如果松弛操作更新了距离
        
        # 如果这次松弛操作中没有任何更新,提前终止
        if not updated:
            break
    
    # 进行负环检测(可选)
    for each edge (u, v) in E[G]:
        if d[v] > d[u] + w(u, v):
            return "Graph contains a negative-weight cycle"
    
    return d, π  # 返回最短路径估计值和前驱

24.1-4 修改 Bellman-Ford 算法,使其对于所有结点 v 来说,如果从源结点 s 到结点 v 的一条路径上存在权重为负值的环路,则将 v.d 的值设置为 -∞
只要将 所有 对于在环路中的点(需要标记)来说 可达的点 设为 -∞ 即可

BELLMAN-FORD'(G, w, s)
    INITIALIZE-SINGLE-SOURCE(G, s)
    for i = 1 to |G.V| - 1
        for each edge (u, v) ∈ G.E
            RELAX(u, v, w)
    for each edge(u, v) ∈ G.E
        if v.d > u.d + w(u, v)
            mark v
    for each vertex u ∈ marked vertices
        DFS-MARK(u)
DFS-MARK(u)
    if u != NIL and u.d != -∞
        u.d = -for each v in G.Adj[u]
            DFS-MARK(v)

2、有向无环图中的单源最短路径问题

1、根据结点的 拓扑排序次序(BF算法之所以需要迭代 N-1 次就是因为是没有顺序的乱撞(暴力循环)) 对带权重的有向无环 G=(V, E) 进行边的松弛操作,我们便可以在 θ(V+E) 时间内 计算出从单个源结点到所有结点之间的最短路径。在有向无环图中,即使存在权重为负值的边,但是 因为没有权重为负值的环路,最短路径都是存在的

2、拓扑排序 是一种将所有顶点线性排序的方法,保证对于每一条有向边 u → v,顶点 u 在排序中出现在顶点 v 之前。拓扑排序的常见算法有两种:Kahn 算法(基于入度)和 深度优先搜索(DFS)算法

在这里插入图片描述
Kahn 算法的伪代码:

def kahn_topological_sort(graph):
    in_degree = {u: 0 for u in graph}  # 初始化所有顶点的入度为 0
    for u in graph:
        for v in graph[u]:
            in_degree[v] += 1  # 计算每个顶点的入度

    queue = [u for u in in_degree if in_degree[u] == 0]  # 找到所有入度为 0 的顶点
    topological_order = []

    while queue:
        u = queue.pop(0)  # 从队列中取出一个顶点
        topological_order.append(u)

        for v in graph[u]:
            in_degree[v] -= 1  # 移除顶点 u 的出边,更新顶点 v 的入度
            if in_degree[v] == 0:
                queue.append(v)  # 如果顶点 v 的入度变为 0,将其加入队列

    # 如果拓扑排序的结果包含了所有顶点,说明排序成功,否则图中有环
    if len(topological_order) == len(in_degree):
        return topological_order
    else:
        return "Graph has a cycle"

时间复杂度:
O(V+E),其中 V 是顶点数,E 是边数。因为我们需要遍历每个顶点和每条边一次
在这里插入图片描述
DFS 算法的伪代码:

def dfs_topological_sort(graph):
    visited = set()  # 记录访问过的顶点
    stack = []  # 栈来存储拓扑排序的结果

    def dfs(v):
        visited.add(v)  # 标记当前顶点已访问
        for neighbor in graph[v]:
            if neighbor not in visited:
                dfs(neighbor)
        stack.append(v)  # 当 v 的所有邻居都已处理,将 v 压入栈

    # 对图中的每个顶点执行 DFS
    for v in graph:
        if v not in visited:
            dfs(v)

    return stack[::-1]  # 返回栈的逆序即为拓扑排序

时间复杂度:同样为 O(V + E)。每个顶点和每条边都只会被访问一次

3、算法 先对有向无环图进行拓扑排序,以便确定 结点之间的一个线性次序。如果 有向无环图包含从结点 u 到结点 v 的一条路径,则 u 在拓扑排序的次序中 位于结点 v 的前面。只需要按照拓扑排序的次序 对结点进行一遍处理即可(确保后面的点 松弛之前 前面的点已经 松弛完毕)。每次对一个结点进行处理时,对从该结点发出的所有边进行松弛操作

DAG-SHORTEST-PATHS(G,w,s)
1 topologically sort the vertices of G
2 INITIALIZE-SINGLE-SOURCE(G,s)
3 for each vertex u, taken in topologically sorted order
4 	for each vertex v∈G.Adj[u]
5 		RELAX(u,v,w)

在这里插入图片描述
算法第 1 行的拓扑排序时间为 Θ(V + E)。第 2 行对 INITIALIZE-SINGLE-SOURCE 的调用所需时间为 Θ(V)。第 3~5 行的 for 循环(外循环)对于每个节点执行一遍,因此,第 4~5 行的 for 循环(内循环)对每条边刚好松弛一次。(注意,我们这里使用了聚合分析。) 因为内循环每次的运行时间为 Θ(1),算法的总运行时间为 Θ(V + E)。对于以邻接链表法表示的图来说,这个时间是线性级

通过聚合分析,可以分析算法的总体成本,而不是逐步分析每个操作的成本
在这里插入图片描述
定理 24.5 如果带权重无环路的有向图 G = (V, E) 有一个源结点 s,则在算法 DAG-SHORTEST-PATHS 终止时,对于所有的结点 v ∈ V,有 v.d = δ(s, v),且前驱子图 Gπ 是一棵最短路径树

证明:因为算法是 按照拓扑排序的次序 来对结点进行处理,所以对路径 p 上的边的放松次序为 (v0, v1), (v1, v2), …, (vk-1, vk)。根据路径松弛性质,对于 i=0, 1, …, k,在算法终止时有 vi.d = δ(s, vi)。最后,根据 前驱子图性质,Gπ 是一棵最短路径树

4、算法 DAG-SHORTEST-PATHS 的一个有趣的应用是在 PERT 图的分析中 进行关键路径的判断。PERT 图是一个有向无环图,在这种图中,每条边代表需要进行的工作,边上的权重 代表执行该工作所需要的时间

如果边 (u, v) 进入结点 v,边 (v, x) 离开结点 v(从结点 v 发出),则工作 (u, v) 必须在工作 (v, x) 前完成。PERT 图中的一条路径代表的是 一个工作执行序列。关键路径 则是该有向无环图中一条最长的路径,该条路径代表执行任何工作序列所需要的最长时间。因此,关键路径上的权重提供的 执行所有工作所需时间的下界

可以使用 下面两种方法中的任意一种 来找到 PERT 图中的关键路径:

  • 将所有权重变为负数,然后运行 DAG-SHORTEST-PATHS
  • 运行 DAG-SHORTEST-PATHS,但在初始化时将 ∞ 替换为 -∞ ,在松弛过程中将“ > ”替换为“ <”

24.2-2 假定将 DAG-SHORTEST-PATHS 的第三行改为:

3 for the first |V| - 1 vertices, taken in topologically sorted order

证明:该算法的正确性保持不变

DAG-SHORTEST-PATHS(G,w,s)
1 topologically sort the vertices of G
2 INITIALIZE-SINGLE-SOURCE(G,s)
3 for each vertex u, taken in topologically sorted order
4 	for each vertex v∈G.Adj[u]
5 		RELAX(u,v,w)

当 到达顶点 v(拓扑排序中的最后一个顶点)时,它必须有出度为 0。否则 将会有一条从较晚顶点指向较早顶点的边,这会导致矛盾。因此,第4行的 for 循环主体 从未针对该最终顶点执行,所以 可以不考虑它

24.2-3 上面描述的 PERT 图的公式有一点不太自然。在一个更自然的结构下,图中的结点 代表要执行的工作,边 代表工作之间的次序限制,即边 (u, v) 表示工作 u 必须在工作 v 之前执行。在这种结构的图中,将权重赋给结点,而不是边。请修改 DAG-SHORTEST-PATHS 过程,使得其可以在线性时间内 找出这种有向无环图中一条最长的路径
在这里插入图片描述

24.2-4 给出一个有效的算法 来计算一个有向无环图中的路径总数
在这里插入图片描述
需要加 1,因为 边本身 也算一条路径,新的路径包括:前面结点的每一条路径 加这条边 + 边本身

3、Dijkstra 算法

1、Dijkstra 算法解决的是 带权重的有向图上 单源最短路径问题,该算法 要求所有边的权重都为非负值。如果所采用的实现方式合适,Dijkstra 算法的运行时间要低于 Bellman-Ford 算法的运行时间
在这里插入图片描述
Dijkstra 算法 在运行过程中维护的关键信息是 一组节点集合 S。从源节点 s 到该集合中每个节点之间的最短路径 已经找到。算法 重复从节点集 V - S 中选择最短路径估计最小的节点 u,将 u 加入到集合 S,然后对所有从 u 发出的边进行松弛。使用一个最小优先队列 Q 来保存节点集合,每个节点的关键值为其 d 值

DIJKSTRA(G, w, s)
1 INITIALIZE-SINGLE-SOURCE(G, s)
2 S =3 Q = G.V
4 while Q ≠ ∅
5 	u = EXTRACT-MIN(Q)
6 	S = S ∪ {u}
7 	for each vertex v ∈ G.Adj[u]
8 		RELAX(u, v, w)

算法所维持的不变式为 Q = V − S,该不变式在算法第 4~8 行的 while 循环过程中保持不变。算法第 3 行对最小优先队列 Q 进行初始化,将所有的结点 V 放在该队列里

结点 u 是集合 V−S 中所有结点的 最小 最短路径估计。然后,在算法的 7~8 行,对所有从结点 u 发出的边 (u, v) 进行松弛操作。如果一条经过结点 u 的路径 能够使得从源结点 s 到结点 v 的最短路径权重 比当前的估计值更小,则 对 v.d 的值和前驱
v.π 的值进行更新
第 3 行之后,再不会 在队列 Q 中插入任何结点,而每个结点从 Q 中被抽取的次数 和 加入集合 S 的次数均为一次,因此,算法第 4~8 行的 while 循环的执行次数刚好为∣V∣ 次
在这里插入图片描述
因为 Dijkstra 算法总是选择集合 V - S 中“最轻” 或 “最近”的结点 来加入到集合 S 中,该算法使用的是贪心策略。关键是证明这样一个事实:该算法 在每次选择结点 u 加入到集合 S 时,有 u.d = δ(s, u)

2、定理 24.6 (Dijkstra 算法的正确性) Dijkstra 算法运行为带权重的有向图 G = (V, E) 时,如果所有权重为非负值,则在算法终止时,对于所有结点 u ∈ V,有 u.d = δ(s, u)

证明:使用下面的循环不变式:
在算法第 4~8 行的 while 语句的每次循环开始前,对于每个结点 v ∈ S,有 v.d = δ(s, v)。只需要证明对于每个结点 u ∈ V,当结点 u 被加到集合 S 时,有 u.d = δ(s, u)

初始化:初始时,S = ∅,因此,循环不变式直接成立
保持:希望证明在每次循环中,对于 加入到集合 S 的结点 u 来说,u.d = δ(s, u)

使用反证法 来证明此结论。设结点 u 是第一个在加入到集合 S 时使得该方程式不成立的结点,即 u.d ≠ δ(s, u)。下面将注意力集中在 把结点 u 加入到集合 S 的这遍循环的开始,并通过对从结点 s 到结点 u 的最短路径进行检查来得出结论 u.d = δ(s, u)
(反证设为 不是直达的,有中转的结点)

由于结点 s 是第一个加入到集合 S 中的结点并且 s.d = δ(s, s) = 0,结点 u 必定与结点 s 不同,即 u ≠ s。因为 u ≠ s,在即将把结点 u 加入到集合 S 时,有 S ≠ ∅。此时,一定存在某条从结点 s 到结点 u 的路径,否则,根据非路径性质将有 u.d = δ(s, u) = ∞,而这将违反我们的假设 u.d ≠ δ(s, u)。因为至少存在一条从 s 到 u 的路径,所以也存在一条从 s 到 u 的最短路径 p

在将结点 u 加入到集合 S 之前,路径 p 连接的是集合 S 中的一个结点(即 s)和 V - S 中的一个结点(即 u)。让我们考虑路径 p 上第一个满足 y ∈ V - S 的结点 y,设 x ∈ S 为结点 y 在路径 p 上的前驱,则如图所示,可以将路径 p 分解为 在这里插入图片描述
在这里插入图片描述
因为选择的结点 u 是 第一个在加入到集合 S 时不满足条件 u.d ≠ δ(s, u) 的结点,在将结点 x 加入到集合 S 时,有 x.d = δ(s, x)。此时,边 (x, y) 将被松弛,根据收敛性质可以得出 在将结点 u 加入到集合 S 时,y.d = δ(s, y)(收敛性质 (引理 24.14) 对于某些结点 u, v ∈ V,如果 s → u → v 是图 G 中的一条最短路径,并且在对边 (u, v) 进行松弛前的任意时间有 u.d = δ(s, u),则在之后的所有时间有 v.d = δ(s, v))

可以通过反证来证明 u.d = δ(s, u)。因为结点 y 是从结点 s 到结点 u 的一条最短路径上位于 u 前面的一个结点,并且所有的边权重均非负,所以有 δ(s, y) ≤ δ(s, u)
在这里插入图片描述
在算法第 5 行选择结点 u 时,结点 u 和 y 都在集合 V - S 里,所以有 u.d ≤ y.d(不然 y 就变成 u 了)。因此,式(24.2)中的两个不等式事实上都是等式,即

y.d = δ(s,y) = δ(s,u) = u.d

因此 u.d = δ(s, u),而这与 所选择的结点 u 矛盾。因此,在结点 u 被加人到集合 S 时有 u.d = δ(s, u),并且该等式在随后的所有时间内都保持成立

终止:在算法终止时,Q = ∅。该事实与之前的不变式 Q = V - S 一起说明了 S = V。因此,对于所有的结点 u ∈ V,有 u.d = δ(s, u)

3、推论 24.7 如果在带权重的有向图 G = (V, E) 上运行 Dijkstra 算法,其中的权重皆为非负值,源结点为 s,则在算法终止时,前驱子图 Gπ 是一棵根结点为 s 的最短路径树

4、分析
该算法 执行三种优先队列操作 来维持最小优先队列:INSERT(算法第 3 行所隐含的操作)、EXTRACT-MIN(算法第 5 行)和 DECREASE-KEY(隐含在算法第 8 行所用的 RELAX 操作中)。该算法 对每个节点调用一次 INSERT 和 EXTRACT-MIN 操作
因为每个节点仅被加入到集合 S 一次
邻接链表 Adj[u] 中的每条边在整个算法运行期间 也只被检查一次(算法第 7~8 行的 for 循环里)。由于所有邻接链表中的边的总数为 |E|,该 for 循环的执行次数一共为 |E| 次,因此该算法调用 DECREASE-KEY 最多 |E| 次

Dijkstra 算法的总运行时间 依赖于 最小优先队列的实现。首先考虑第一种情况:通过利用节点的编号为 1~|V| 来维持最小优先队列。在这种情况下,将 v.d 的值存放在数组的第 v 个记录里。每次 INSERT 和 DECREASE-KEY 操作的执行时间为 O(1),每次 EXTRACT-MIN 的操作时间为 O(V)(因为需要搜索整个数组),算法的总运行时间为 O(V2 + E) = O(V2)

如果 讨论的是稀疏图,特别地,如果 E = o(V2/ lgV),则可以 使用二叉堆 来实现最小优先队列,从而改善算法的运行时间。每次 EXTRACT-MIN 操作的执行时间为 O(lgV)。和前面一样,一共有 |V| 次这样的操作。构建最小二叉堆的成本为 O(V)。每次 DECREASE-KEY 操作的执行时间为 O(lgV),而最多有 |E| 次这样的操作。因此,若所有节点 都可以从源节点到达,算法的总运行时间为 O((V+E) lgV),即 O(E lgV)。若 E = o(V2 / lgV),则该时间成本相对于直接实现的 O(V2) 成本有所改善(在 E = o(V2 / lgV) 时,O(E lgV) 小于 O(V2))

可以将 Dijkstra 算法的运行时间 改进到 O(VlgV + E),方法是使用斐波那契堆 来实现最小优先队列。在这种实现下,每次 EXTRACT-MIN 的摊还代价为 O(lgV),每次 DECREASE-KEY 的摊还代价 改进为 O(1)。斐波那契堆 提出的动机 就是因为人们观察到 Dijkstra 算法调用的 DECREASE-KEY 操作通常比 EXTRACT-MIN 操作更多,因此,任何能够将 DECREASE-KEY 操作的摊还代价降低到 o(lgV) 而又不增加 EXTRACT-MIN 操作的摊还代价的方法 都将产生比二叉堆的渐近性能更优的实现

5、Dijkstra 算法 既类似于广度优先搜素,也有点类似于计算最小生成树的 Prim 算法
它与广度优先搜索的相似点 在于集合 S 对应的是广度优先搜索中的黑色结点集合:正如集合 S 中的结点的最短路径权重 已经计算出来一样,在广度优先搜索中,黑色结点的正确的广度优先距离 也已经计算出来
在这里插入图片描述

Dijkstra 算法像 Prim 算法的地方是,两个算法 都使用最小优先队列来寻找给定集合(Dijkstra 算法中的 S 集合 与 Prim 算法中逐步增长的树)之外的 “最轻” 结点,将该结点加入到集合里,并对位于 集合外面的结点的权重进行相应调整

24.3-2 举出一个包含负权重的有向图,使得 Dijkstra 算法在其上运行时将产生不正确的结果
定理 24.6 的证明也不再适用,因为 无法保证
δ(s, y) ≤ δ(s, u)

24.3-4 Gaedel 教授编写了一个程序,他声称 该程序实现了 Dijkstra 算法。对于每个结点 v∈V,他的程序在任何时候都有 d[v] ≤ δ(s, v)。然而,他发现这个程序从未更新过 π[v]。Gaedel 教授的程序正确吗
在这里插入图片描述
24.3-5 Newman 教授觉得自己的发现了 Dijkstra 算法的一个更简单的证明。他声称 Dijkstra 算法对最短路径上的每条边的松弛次序 与该条边在这条最短路径中的次序相同,因此,路径松弛性质 适用于 从源结点可以到达的所有结点。请构造一个有向图来说明 Dijkstra 算法并不一定按照最短路径中边出现的次序来对边进行松弛,从而证明教授是错的

Newman 教授的论断是错误的。Dijkstra 算法不一定按照 最短路径上边的顺序 进行松弛,松弛的顺序 取决于 优先队列中顶点的选择顺序,Dijkstra 算法 会优先处理当前最小距离的顶点,但这并不保证 在同一条路径上的所有边会按顺序松弛
在这里插入图片描述
24.3-6 给定有向图 G = (V, E),每条边 (u, v) ∈ E 有一个关联值 r(u, v),该关联值是一个实数,其范围为 0 ≤ r(u, v) ≤ 1,其代表的意思是从结点 u 到结点 v 之间的通信链路不失效的概率,并且假定这些概率之间相互独立。请给出一个有效的算法 来找到任意两个结点之间最可靠的通信链路
在这里插入图片描述
24.3-8 给定带权重的有向图 G = (V, E),其权重函数为 w: E→{0,1,2,…,W},这里 W 为某个非负整数。请修改 Dijkstra 算法来计算 从给定源结点 s 到所有结点之间的最短路径。该算法时间应为 O(WV + E)
在这里插入图片描述
24.3-10 假设 给定带权重的有向图 G = (V, E),从源结点 s 发出的边的权重可以为负值,而其他所有边的权重 全部是非负值,同时,图中不含权重为负值的环路。证明:Dijkstra 算法可以正确计算出 从源结点 s 到所有其他结点之间的最短路径
在这里插入图片描述
后续步骤中,所有边的权重均为非负,已确定的最短路径不会被再次更新

4、差分约束和最短路径

讨论线性规划中 的一个特例,该特例 可以 被归约为单源最短路径问题。这样,可以通过运行 Bellman-Ford 算法来解决单源最短路径问题,从而解决 这种特殊的线性规划问题

1、线性规划
可以 将某个给定问题 看做是一个多项式规模的线性规划问题,则可以 立即获得一个多项式时间的算法解。其次,对于线性规划的许多特殊情况,存在着更快的算法。例如,单源单目的地最短路径问题

并不关注目标函数,而仅仅是希望找到一个可行解

2、差分约束系统
在一个差分约束系统中,线性规划矩阵 A 的每一行包括一个 1 和一个 −1,其他所有项皆为 0。因此,由 Ax ≤ b 所给出的约束条件变为 m 个涉及 n 个变量的差额限制条件,其中的每个约束条件是 如下所示的简单线性不等式:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
一个可能答案是 x = (−5, −3, 0, −1, −4),这个问题的答案有多个。另一个答案是 x = (0, 2, 5, 4, 1)。这两个答案之间存在着关联关系:向量 x′ 中每个元素比向量 x 中的对应元素的取值大 5

引理 24.8
设向量 x = (x1, x2, …, xn) 为差分约束系统 Ax ≤ b 的一个解,设 d 为任意数,则 x + d = (x1 + d, x2 + d, …, xn + d) 也是该差分约束系统的一个解

证明 对于每个 xi 和 xj,我们有 在这里插入图片描述
差分约束系统 在许多不同的应用里都会出现。例如,未知变量 xi 可能代表的是事件发生的时间。每个约束条件给出的是 在两个事件之间必须间隔的最短时间 或 最长时间

3、约束图
将 m × n 的线性规划矩阵 A 看做成一张由 n 个结点和 m 条边构成的图的邻接矩阵的转置
在这里插入图片描述
图中的每个结点 vi 对应 n 个未知变量 xi 中的一个。图中的每条有向边 则对应 m 个不等式中的一个
给定差分约束系统 Ax ≤ b,其对应的约束图是 一个带权重的有向图 G = (V, E),这里:
在这里插入图片描述
约束图 包含一个额外的结点 v0,用来保证 图中至少存在一个结点,从其出发可以到达所有的其他结点。因此,结点集合 V 由代表每个变量 xi 的结点 vi 和额外的结点 v0 所组成。边集合 E 包含的是代表每个差分约束的边,再加上边 (v0, vi),i = 1,2,…,n。如果 xi – xj ≤ bk 是一个差分约束条件,则边 (vi, vj) 的权重为 w(vi, vj) = bk。所有从结点 v0 发出的边的权重为 0
在这里插入图片描述
下面的定理 将证明可以通过 在对应的约束图中 寻找最短路径权重 来找到一个差分约束系统的一个解

定理 24.9 给定差分约束系统 Ax ≤ b,设 G = (V, E) 是该差分约束系统所对应的约束图。如果图 G 不包含权重为负值的环路,则
在这里插入图片描述
是该系统的一个可行解。如果图 G 包含权重为负值的环路,则该系统没有可行解

证明:根据三角不等式,δ(v0, vj) ≤ δ(v0, vi) + w(vi, vj),即 δ(v0, vj) − δ(v0, vi) ≤ w(vi, vj)。因此,如果令 xi = δ(v0, vi) 和 xj = δ(v 0 ,vj),则 xi 和 xj 满足对应边 (vi, vj) 的差分约束条件 xj − xi ≤ w(vi, vj)
如果约束图包含权重为负值的环路,则差分约束系统没有可行解。不失一般性,设权重为负值的环路为 在这里插入图片描述
假设向量 x 有一个满足上述 k 个不等式的解,则这个解 必须同时满足将 k 个不等式加起来之后 形成的新不等式(只剩一头一尾)。如果将不等式的左边 进行求和,每个未知变量 xi 被加进来一次,被减去一次 (vi = vk 意味着 x1 = xk,因为是环),使得左边的和为 0。不等式右边的和为 wc,因此有 0 ≤ wc。但是 c 是一个权重为负值的环路,因此 w© < 0,这样我们得出矛盾:0 ≤ w(c) < 0

4、求解差分约束系统
可以使用 Bellman-Ford 算法 来求解差分约束系统。因为约束图 包含从源点 v0 到所有其他结点的边,任何权重为负值的环路 都可以从结点 v0 到达。如果 Bellman-Ford 算法返回 TRUE 值,则最短路径权重给出的是 该系统的一个可行解。如果 Bellman-Ford 算法返回 FALSE 值,则差分约束系统 没有可行解

一个有 n 个未知变量和 m 个约束条件的差分约束系统 所生成的约束图有 n+1 个结点 和 n+m 条边。因此,使用 Bellman-Ford 算法,可以在 O((n+1) (n+m)) = O(n2 + nm) 时间内 求解该系统

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

电压跟随器的作用是什么?

电压跟随器&#xff08;也称为单位增益放大器、缓冲放大器和隔离放大器&#xff09;是一种电压增益为 1 的运算放大器电路。这意味着运算放大器不会对信号进行任何放大。 之所以称为电压跟随器&#xff0c;是因为输出电压直接跟随输入电压&#xff0c;即输出电压与输入电压相同…

测试工程师学历路径:从功能测试到测试开发

现在软件从业者越来越多&#xff0c;测试工程师的职位也几近饱和&#xff0c;想要获得竞争力还是要保持持续学习。基本学习路径可以从功能测试-自动化测试-测试开发工程师的路子来走。 功能测试工程师&#xff1a; 1、软件测试基本概念&#xff1a; 学习软件测试的定义、目的…

产品探秘|开物——面向AI原生和云原生网络研究的首选科研平台

在当今高速发展的信息技术领域&#xff0c;特别是对于那些致力于前沿科技探索与实践的高校而言&#xff0c;拥有一款能够支持复杂网络业务研究与开发的平台至关重要。开物™数据网络开发平台&#xff08;Data Network Development Platform&#xff0c;简称DNDP&#xff09;&am…

Marin说PCB之在CST软件中如何搭建两端子电容器--03

上期文章的结尾讲到的问题不知诸位大神们是否还记得&#xff1a;就是一颗新电容器的物料是否可以完全替换掉之前的Murata家的这个GRT033D70E105ME18物料&#xff1f; 小编我也看了私信有不少的人认为是可以替换掉的&#xff0c;原因是两个电容封装&#xff0c;容值都是一样的&a…

停止向供应商提供您的数据

组织管理其数据基础设施的方式正在发生重大转变。越来越多的公司认识到存储和计算分离的优势&#xff0c;从而获得更好的性能、成本节约和可扩展性。这一趋势是由 AI 和 ML 工作负载日益复杂所推动的&#xff0c;这些工作负载需要灵活、高性能的系统。Databricks 首席执行官 Al…

Java短信验证码

想利用java给用户发送短信的话&#xff0c;需要我们与电信、移动、联通三大巨头合作&#xff08;其实还有广电&#xff0c;但是比较少用&#xff09;&#xff0c;让它帮你发信息&#xff0c;当然直接与它合作显然是不现实的&#xff0c;所以我们要借助第三方的短信平台来替我们…

el-tree父子不互相关联时,手动实现全选、反选、子级全选、清空功能

el-tree父子不互相关联时&#xff0c;手动实现全选、反选、子级全选、清空功能 1、功能实现图示 2、实现思路 当属性check-strictly为true时&#xff0c;父子节点不互相关联&#xff0c;如果需要全部选中或选择某一节点下的全部节点就必须手动选择每个节点&#xff0c;十分麻…

什么是科技与艺术相结合的异形创意圆形(饼/盘)LED显示屏

在当今数字化与创意并重的时代&#xff0c;科技与艺术的融合已成为推动社会进步与文化创新的重要力量。其中&#xff0c;晶锐创显异形创意圆形LED显示屏作为这一趋势下的杰出代表&#xff0c;不仅打破了传统显示设备的形态束缚&#xff0c;更以其独特的造型、卓越的显示效果和广…

使用AI赋能进行软件测试-文心一言

1.AI赋能的作用 提高速度和效率缺陷预测与分析 2.AI互动指令格式--文心一言 角色、指示、上下文例子、输入、输出 a 直接问AI 针对以下需求&#xff0c;设计测试用例。 需求&#xff1a; 1、账号密码登录系统验证账号和密码的正确性。 验证通过,用户登录成功,进入个人中心;验…

无刷直流电动机的匝间绝缘测试优化

近年来&#xff0c;随着消费者对高效、快速干发需求的增加&#xff0c;高速电吹风逐渐成为市场的宠儿。高速电吹风的关键技术之一便是无刷直流电动机&#xff0c;其转速可以高达100,000转/分钟以上&#xff0c;电压为DC310V。相比传统电吹风&#xff0c;高速电吹风在效率和用户…

Prometheus 监控平台(Prometheus Monitoring Platform)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

灰光模块,彩光模块-介绍

1. 引用 知识分享系列一&#xff1a;5G基础知识-CSDN博客 5G前传的最新进展-CSDN博客 灰光和彩光_通信行业5G招标系列点评之二&#xff1a;一文读懂5G前传-光纤、灰光、彩光、CWDM、LWDM、MWDM...-CSDN博客 ADOP带你了解&#xff1a;CWDM、DWDM、MWDM、LWDM&#xff1a;快速…

ffmpeg实现视频的合成与分割

视频合成与分割程序使用 作者开发了一款软件&#xff0c;可以实现对视频的合成和分割&#xff0c;界面如下&#xff1a; 播放时&#xff0c;可以选择多个视频源&#xff1b;在选中“保存视频”情况下&#xff0c;会将多个视频源合成一个视频。如果只取一个视频源中一段视频…

jmeter之TPS计算公式

需求&#xff1a; 如何确定环境当中的TPS指标 PV:&#xff08;Page View&#xff09;即页面访问量&#xff0c;每打开一次页面PV计数1&#xff0c;刷新页面也是。PV只统计页面访问次 数。 UV(Unique Visitor),唯一访问用户数&#xff0c;用来衡量真实访问网站的用户数量。 一般…

基于matlab交通标志识别系统用的APP designer设计的gui界面 交互原理:bp神经网络-训练好图像处理有灰度化-二值化-颜色区域定位识别

基于MATLAB的交通标志识别系统是一个实用的工具&#xff0c;用于识别道路交通标志。该系统结合了图像处理技术和BP神经网络模型&#xff0c;可以在给定的图像中定位并识别交通标志。通过使用MATLAB的App Designer工具&#xff0c;系统还提供了一个交互式的图形用户界面&#xf…

OpenAI发布o1大模型,突破LLM推理极限,弥补了之前在数学、科学和代码方面的不足

在北京时间9月13日凌晨&#xff0c;OpenAI正式发布了一系列全新的AI大模型【o1-preview 和 o1-mini】&#xff0c;专门针对复杂问题的解决。这一发布标志着一次重要的突破&#xff0c;新模型能够实现复杂的推理能力&#xff0c;通用模型在解决科学、代码和数学等领域中的难题方…

Linux 防火墙:iptables (一)

文章目录 iptables 概述netfilter 与 iptables 的关系 四表五链规则表规则链数据包处理的优先顺序与规则链匹配顺序规则表的优先顺序规则链的匹配顺序规则链内的匹配顺序匹配流程示意图 安装与格式iptables 的安装iptables 防火墙的配置方法iptables 命令行配置方法常用的控制类…

TestCraft - GPT支持的测试想法生成器和自动化测试生成器

在当今快速变化的软件开发世界中&#xff0c;自动化测试已成为确保软件质量的关键环节。而随着AI技术的进步&#xff0c;越来越多的工具开始引入人工智能&#xff0c;来辅助生成测试用例和自动化测试脚本。其中&#xff0c;TestCraft&#xff0c;作为一款GPT支持的测试想法生成…

【数据结构】双向链表专题

目录 1.双向链表的结构 2.双向链表的实现 2.1初始化 以参数的形式初始化链表&#xff1a; 以返回值的形式初始化链表&#xff1a; 2.2尾插 2.3打印 2.4头插 2.5尾删 2.6头删 2.7查找 2.8在指定位置之后插入数据​编辑 2.9删除pos节点 2.10销毁 3.整理代码 3.1…

Unity笔记:ScrollRect代码阅读

大体流程 Unity Docs - UGUI | Class ScrollRect 总的说 自身不负责Rebuild&#xff0c;设置脏之后交由LayoutRebuilder注册到CanvasUpdateRegistry里待rebuild的集合在固定时机统一Rebuild。自身只在Prelayout和Postlayout做一下数据准备和数据更新 自身的ICanvasElement.…