您的位置:首页 > 新闻 > 热点要闻 > 杭州网站建设_全网最新首码项目_百度浏览器app_淘宝怎么提高关键词搜索排名

杭州网站建设_全网最新首码项目_百度浏览器app_淘宝怎么提高关键词搜索排名

2024/12/22 12:59:15 来源:https://blog.csdn.net/qq_42952637/article/details/142196148  浏览:    关键词:杭州网站建设_全网最新首码项目_百度浏览器app_淘宝怎么提高关键词搜索排名
杭州网站建设_全网最新首码项目_百度浏览器app_淘宝怎么提高关键词搜索排名

代码随想录训练营 Day60打卡 图论part10

一、Bellman_ford 队列优化算法(又名SPFA)

例题:卡码94. 城市间货物运输 I

题目描述
某国为促进城市间经济交流,决定对货物运输提供补贴。共有 n 个编号为 1 到 n 的城市,通过道路网络连接,网络中的道路仅允许从某个城市单向通行到另一个城市,不能反向通行。
网络中的道路都有各自的运输成本和政府补贴,道路的权值计算方式为:运输成本 - 政府补贴。权值为正表示扣除了政府补贴后运输货物仍需支付的费用;权值为负则表示政府的补贴超过了支出的运输成本,实际表现为运输过程中还能赚取一定的收益。
请找出从城市 1 到城市 n 的所有可能路径中,综合政府补贴后的最低运输成本。如果最低运输成本是一个负数,它表示在遵循最优路径的情况下,运输过程中反而能够实现盈利。
城市 1 到城市 n 之间可能会出现没有路径的情况,同时保证道路网络中不存在任何负权回路。
输入描述
第一行包含两个正整数,第一个正整数 n 表示该国一共有 n 个城市,第二个整数 m 表示这些城市中共有 m 条道路。
接下来为 m 行,每行包括三个整数,s、t 和 v,表示 s 号城市运输货物到达 t 号城市,道路权值为 v (单向图)。
输出描述
如果能够从城市 1 到连通到城市 n, 请输出一个整数,表示运输成本。如果该整数是负数,则表示实现了盈利。如果从城市 1 没有路径可达城市 n,请输出 “unconnected”。
输入示例
6 7
5 6 -2
1 2 1
5 3 1
2 5 2
2 4 -3
4 6 4
1 3 5
输出示例
1
提示信息

示例中最佳路径是从 1 -> 2 -> 5 -> 6,路上的权值分别为 1 2 -2,最终的最低运输成本为 1 + 2 + (-2) = 1。
示例 2:
4 2
1 2 -1
3 4 -1
在此示例中,无法找到一条路径从 1 通往 4,所以此时应该输出 “unconnected”。

SPFA(Shortest Path Faster Algorithm)是一种 Bellman-Ford 算法的队列优化版本。它的主要思想是:使用一个队列来保存已经更新过的节点,并且仅对这些节点进行松弛操作,而不是对所有边进行松弛,从而加速最短路径的计算。这样可以减少不必要的松弛操作。

算法流程

  1. 初始化距离数组,将起点的距离设为 0,其他节点的距离设为正无穷大。 将起点加入队列。
  2. 每次从队列中取出一个节点,遍历与该节点相连的所有边,如果可以进行松弛,则更新相邻节点的最短路径,并将该相邻节点加入队列(如果它不在队列中的话)。
  3. 重复步骤 3,直到队列为空。

代码实现

from collections import deque, defaultdict
import sys# 定义一个边结构,用于存储每条边的信息
class Edge:def __init__(self, to, weight):self.to = to  # 边的目标节点self.weight = weight  # 边的权重def spfa(n, edges, start, end):# 初始化邻接表graph = defaultdict(list)for p1, p2, val in edges:graph[p1].append(Edge(p2, val))# 初始化最短距离数组,所有节点的最短距离都为无穷大minDist = [sys.maxsize] * (n + 1)minDist[start] = 0  # 起点的距离设为0# 初始化队列,并将起点加入队列queue = deque([start])isInQueue = [False] * (n + 1)  # 用于记录节点是否在队列中,避免重复加入isInQueue[start] = Truewhile queue:# 从队列中取出一个节点node = queue.popleft()isInQueue[node] = False  # 节点取出后取消标记# 遍历该节点的所有相邻边,进行松弛操作for edge in graph[node]:to = edge.toweight = edge.weight# 如果可以通过 node 到达 to 的更短路径,则更新最短距离if minDist[to] > minDist[node] + weight:minDist[to] = minDist[node] + weight# 如果相邻节点不在队列中,则将其加入队列if not isInQueue[to]:queue.append(to)isInQueue[to] = True# 如果终点的最短距离仍为无穷大,说明无法到达终点if minDist[end] == sys.maxsize:print("unconnected")else:print(minDist[end])  # 输出到终点的最短路径长度# 输入处理
n, m = map(int, input().split())  # n 是节点数,m 是边数
edges = []
for _ in range(m):p1, p2, val = map(int, input().split())  # 输入边信息edges.append((p1, p2, val))start = 1  # 起点设为节点1
end = n    # 终点设为节点n# 调用SPFA算法
spfa(n, edges, start, end)

详细解释:

  • Edge类:定义了一个边结构,用于存储边的目标节点和边的权重。
  • spfa 函数:这是 SPFA 算法的主体函数,接收节点数 n、边列表 edges、起点 start 和终点 end作为参数,返回从起点到终点的最短路径距离。
    graph:邻接表,用来存储每个节点的相邻边。
    minDist:最短距离数组,初始时所有节点的最短距离都设置为无穷大,起点的最短距离为 0。
    queue:队列,用于存储更新过的节点,起点首先加入队列。
    isInQueue:标记数组,表示当前节点是否在队列中,避免重复入队。
    while 循环:不断从队列中取出节点并进行松弛操作,直到队列为空。
    松弛操作:如果通过某条边可以找到更短路径,则更新目标节点的最短路径,并将其加入队列。
    最终结果:如果终点的最短路径值仍为无穷大,则表示无法到达终点;否则输出终点的最短路径长度。

复杂度分析:

  • 时间复杂度:最坏情况下时间复杂度为 O(V * E),其中 V 为节点数,E 为边数。但由于队列优化,通常性能会比原始的
    Bellman-Ford 更优。
  • 空间复杂度:O(V + E),用来存储图和队列。

SPFA 的优化机制
SPFA 的核心优化思想是只松弛那些可能带来改进的边。它使用队列来跟踪已经发生更新的节点(称为活跃节点),从这些节点出发继续尝试松弛邻接边。这样就避免了对所有边的重复松弛。

关键优化点:

  1. 只松弛有效边:

Bellman-Ford 每一轮都会检查所有边,而 SPFA 只松弛从“活跃节点”出发的边。这意味着在很多情况下,SPFA 需要松弛的边数量会远小于 Bellman-Ford 的所有边。

  1. 跳过不活跃节点:

只有当某个节点的最短路径被更新时,SPFA 才会将它的相邻节点加入队列。因此,如果某些节点的路径没有更新,它们不会被加入队列进行松弛操作。

  1. 动态松弛顺序:

Bellman-Ford 是静态的,每一轮都对所有边进行遍历;而 SPFA 是动态的,按需对队列中的节点及其相邻边进行松弛。这种按需松弛会根据路径变化自适应调整执行顺序,减少无效计算。

时间复杂度分析

  1. 最坏情况:

    在最坏情况下,SPFA 可能需要对每条边执行 𝑉次松弛操作,这样最坏情况下的时间复杂度仍然是:𝑂(𝑉×𝐸)
    这与 Bellman-Ford 算法在最坏情况下一样。

  2. 平均情况:

    在许多实际应用中,SPFA 通常可以在远少于 𝑉−1 轮迭代的情况下收敛。这是因为一旦最短路径收敛,队列中的节点数量会迅速减少,因此减少了不必要的松弛操作。对于稀疏图(边较少)和具有良好结构的图,SPFA 的平均时间复杂度更接近于线性:𝑂(𝐸) 。
    也就是只需对每条边松弛一次或几次即可收敛。

卡码题目链接
题目文章讲解

二、Bellman-Ford 之判断负权回路

例题:卡码94. 城市间货物运输 I

题目描述
某国为促进城市间经济交流,决定对货物运输提供补贴。共有 n 个编号为 1 到 n 的城市,通过道路网络连接,网络中的道路仅允许从某个城市单向通行到另一个城市,不能反向通行。
网络中的道路都有各自的运输成本和政府补贴,道路的权值计算方式为:运输成本 - 政府补贴。权值为正表示扣除了政府补贴后运输货物仍需支付的费用;权值为负则表示政府的补贴超过了支出的运输成本,实际表现为运输过程中还能赚取一定的收益。
然而,在评估从城市 1 到城市 n 的所有可能路径中综合政府补贴后的最低运输成本时,存在一种情况:图中可能出现负权回路。负权回路是指一系列道路的总权值为负,这样的回路使得通过反复经过回路中的道路,理论上可以无限地减少总成本或无限地增加总收益。为了避免货物运输商采用负权回路这种情况无限的赚取政府补贴,算法还需检测这种特殊情况。
请找出从城市 1 到城市 n 的所有可能路径中,综合政府补贴后的最低运输成本。同时能够检测并适当处理负权回路的存在。
城市 1 到城市 n 之间可能会出现没有路径的情况
输入描述
第一行包含两个正整数,第一个正整数 n 表示该国一共有 n 个城市,第二个整数 m 表示这些城市中共有 m 条道路。
接下来为 m 行,每行包括三个整数,s、t 和 v,表示 s 号城市运输货物到达 t 号城市,道路权值为 v。
输出描述
如果没有发现负权回路,则输出一个整数,表示从城市 1 到城市 n 的最低运输成本(包括政府补贴)。如果该整数是负数,则表示实现了盈利。如果发现了负权回路的存在,则输出 “circle”。如果从城市 1 无法到达城市 n,则输出 “unconnected”。
输入示例
4 4
1 2 -1
2 3 1
3 1 -1
3 4 1
输出示例
circle
提示信息
路径中存在负权回路,从 1 -> 2 -> 3 -> 1,总权值为 -1,理论上货物运输商可以在该回路无限循环赚取政府补贴,所以输出 “circle” 表示已经检测出了该种情况。

Bellman-Ford 算法是一种用于寻找图中单源最短路径的算法,允许图中存在负权边,但不能处理图中存在负权回路的情况。在 Bellman-Ford 算法中,松弛每条边 n-1 次(n 是节点数)可以确保找到从起点到所有其他节点的最短路径。如果在第 n 次松弛后仍然发生了变化,说明图中存在负权回路。

负权回路的影响

在本题中,由于存在负权回路,我们可以在回路中绕圈,继续减小路径的代价,导致最短路径可以无限减小。这意味着存在负权回路的图是没有最小路径解的,因为可以通过绕圈使路径代价无限减少。因此,解决本题的关键在于:在执行完 n-1 次松弛之后,再执行第 n 次松弛,如果最短路径数组 minDist 仍然发生变化,则说明存在负权回路。

思路总结

  1. 初始化:从起点到其他所有节点的距离初始化为无穷大(inf),到起点自身的距离为 0。
  2. 松弛过程:对所有边进行 n-1 次松弛操作,确保找到从起点到其他节点的最短路径。
  3. 判断负权回路:在第 n 次松弛时,如果距离数组发生变化,则说明图中存在负权回路。

代码实现

import sysdef main():input = sys.stdin.readdata = input().split()index = 0# 读取节点数 n 和边数 mn = int(data[index])index += 1m = int(data[index])index += 1# 记录所有的边,grid 是一个包含 [p1, p2, val] 的列表,表示从 p1 到 p2,权值为 valgrid = []for i in range(m):p1 = int(data[index])index += 1p2 = int(data[index])index += 1val = int(data[index])index += 1grid.append([p1, p2, val])start = 1  # 起点是节点 1end = n    # 终点是节点 n# minDist 数组存储起点到每个节点的最短距离,初始为无穷大minDist = [float('inf')] * (n + 1)minDist[start] = 0  # 起点到自身的距离为 0flag = False  # 用于标记是否有负权回路# 松弛 n 次,前 n-1 次是正常的 Bellman-Ford 松弛,第 n 次用于检查负权回路for i in range(1, n + 1):  # 从 1 到 n,松弛 n 次for side in grid:from_node = side[0]  # 边的起点to = side[1]         # 边的终点price = side[2]      # 边的权值# 如果松弛次数小于 n 次,正常松弛if i < n:# 如果起点到 from_node 的距离不是无穷大,且通过 from_node 能到达 to,并且能更新到更小的距离,则进行松弛if minDist[from_node] != float('inf') and minDist[to] > minDist[from_node] + price:minDist[to] = minDist[from_node] + priceelse:# 第 n 次松弛,用于检查是否存在负权回路if minDist[from_node] != float('inf') and minDist[to] > minDist[from_node] + price:flag = True  # 如果还能松弛,说明存在负权回路# 如果存在负权回路if flag:print("circle")# 如果起点和终点不连通,即终点的最短路径还是无穷大elif minDist[end] == float('inf'):print("unconnected")else:# 否则输出起点到终点的最短路径print(minDist[end])if __name__ == "__main__":main()

卡码题目链接
题目文章讲解

三、Bellman-Ford 之单源有限最短路

例题:卡码96. 城市间货物运输 III

题目描述
某国为促进城市间经济交流,决定对货物运输提供补贴。共有 n 个编号为 1 到 n 的城市,通过道路网络连接,网络中的道路仅允许从某个城市单向通行到另一个城市,不能反向通行。
网络中的道路都有各自的运输成本和政府补贴,道路的权值计算方式为:运输成本 - 政府补贴。权值为正表示扣除了政府补贴后运输货物仍需支付的费用;权值为负则表示政府的补贴超过了支出的运输成本,实际表现为运输过程中还能赚取一定的收益。
请计算在最多经过 k 个城市的条件下,从城市 src 到城市 dst 的最低运输成本。
输入描述
第一行包含两个正整数,第一个正整数 n 表示该国一共有 n 个城市,第二个整数 m 表示这些城市中共有 m 条道路。
接下来为 m 行,每行包括三个整数,s、t 和 v,表示 s 号城市运输货物到达 t 号城市,道路权值为 v。
最后一行包含三个正整数,src、dst、和 k,src 和 dst 为城市编号,从 src 到 dst 经过的城市数量限制。
输出描述
输出一个整数,表示从城市 src 到城市 dst 的最低运输成本,如果无法在给定经过城市数量限制下找到从 src 到 dst 的路径,则输出 “unreachable”,表示不存在符合条件的运输方案。
输入示例
6 7
1 2 1
2 4 -3
2 5 2
1 3 5
3 5 1
4 6 4
5 6 -2
2 6 1
输出示例
0
提示信息
从 2 -> 5 -> 6 中转一站,运输成本为 0。

本题是 Bellman-Ford 算法的一个变种,需要计算从起点到终点,**最多经过 k 个城市(即 k + 1 条边)**的最短路径。核心思想和 Bellman-Ford 算法类似,不同之处在于我们需要限制松弛操作的次数,即最多只松弛 k + 1 次。

问题背景

  • 每次松弛操作,相当于对所有边进行一次松弛,尝试更新当前已知的最短路径。
  • 在标准的 Bellman-Ford 中,我们对所有边松弛 n-1 次即可得到从起点到所有其他节点的最短路径,其中 n
    是节点数量,因为最长路径最多经过 n-1 条边。
  • 而在本题中,我们只需要考虑最多经过 k + 1 条边的路径,因此只需松弛 k + 1 次。

问题关键
在松弛过程中,更新节点的距离时,我们要基于上一次松弛后的最短路径来进行更新。因此,每次松弛时要基于前一次的结果(即 minDist_copy 数组)进行更新,而不是当前的结果。

思路总结

  1. 初始化 minDist 数组,表示从起点到各个节点的最短路径,起点到自身距离为 0,其他节点初始化为无穷大(表示不可达)。
  2. 松弛 k + 1 次,每次松弛时基于上一次松弛的结果更新最短路径。
  3. 在松弛结束后,如果终点的最短路径依然为无穷大,说明不可达,输出 “unreachable”。否则输出最短路径。

代码实现

import sysdef main():input = sys.stdin.readdata = input().split()# 读取节点数 n 和边数 mindex = 0n = int(data[index])index += 1m = int(data[index])index += 1# 读取边信息 (p1, p2, val)grid = []for i in range(m):p1 = int(data[index])index += 1p2 = int(data[index])index += 1val = int(data[index])index += 1grid.append([p1, p2, val])# 读取起点 src,终点 dst,最多经过的节点数 ksrc = int(data[index])index += 1dst = int(data[index])index += 1k = int(data[index])index += 1# 初始化最短路径数组,minDist[src] = 0 表示起点到自身的距离为 0minDist = [float('inf')] * (n + 1)minDist[src] = 0# 用来记录上一次遍历的最短路径结果minDist_copy = minDist[:]# 松弛 k + 1 次for i in range(k + 1):minDist_copy = minDist[:]  # 记录上一次松弛的最短路径结果for side in grid:from_node = side[0]to_node = side[1]price = side[2]# 如果 from_node 可达,并且可以更新到更短的路径,更新 minDistif minDist_copy[from_node] != float('inf') and minDist[to_node] > minDist_copy[from_node] + price:minDist[to_node] = minDist_copy[from_node] + price# 输出结果if minDist[dst] == float('inf'):print("unreachable")  # 如果终点不可达,输出 unreachableelse:print(minDist[dst])   # 否则输出起点到终点的最短路径if __name__ == "__main__":main()
  • 时间复杂度:本算法的时间复杂度为 O(k * m),其中 k 是松弛次数,m 是图中边的数量。相较于标准的 Bellman-Ford 算法时间复杂度 O(n * m),这是合理的限制条件,因为我们只松弛 k + 1 次。
  • 空间复杂度:主要存储 minDist 和 minDist_copy 两个数组,空间复杂度为 O(n)。

卡码题目链接
题目文章讲解

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com