图论-最短路算法

1. Floyd算法

  • 作用:用于求解多源最短路,可以求解出任意两点的最短路

  • 利用动态规划只需三重循环即可(动态规划可以把问题求解分为多个阶段)
  • 定义dp[k][i][j]表示点i到点j的路径(除去起点终点)中最大编号不超过k的情况下,点i到点j的最短距离。
  • 当加入第k个点作为i到j的中间点:dp[k]][i][j] = min(dp[k - 1]][i][j]], dp[k - 1][i][k] + dp[k - 1][k][j])

发现可以使用滚动数组优化第一维度:

dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j])

枚举所有k,判断是否可以作为中间点,可以作为中间点则优化最短路。

初始化:如果<i, j>无边,则dp[i][j] = INF, 有边则等于边权;dp[i][i] = 0(自己到自己是不用走的)

为了理解更深刻,简单举个例子:

各点之间的关系用邻接矩阵保存(下图中又两个邻接矩阵,一个是两点之间的最短距离,还有一个是两点之间的最短路中经过的节点。)

更新

每次基于之前能找到的最短路径,如果比它短就更新。

以2号节点作为中转站是基于1号节点作为中转站的,经过n轮递推就可以得到最终答案(任意两点的最短路)

例题:

蓝桥1121

为什么先遍历k,之后遍历i,j?

因为要符合顺序,遍历完中间点后, 就要遍历邻接矩阵,进行最短距离的更新。

import os
import sys

# 请在此输入您的代码
n, m, q = map(int, input().split())
INF = 10 ** 18
# dp[i][j]表示i到j的最短路
dp = [[INF] * (n + 1) for i in range(n + 1)] # 初始值设较大值
for i in range(1, n + 1):
  dp[i][i] = 0 # 自己到自己的距离为0
for _ in range(m):
  u, v, w = map(int, input().split())
  dp[u][v] = dp[v][u] = min(dp[u][v], w) # 双向边/无向边(可能有重边)

# Floyd算法模板
# dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j])
for k in range(1, n + 1):
  for i in range(1, n + 1):
    for j in range(1, n + 1):
      dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j])

for _ in range(q):
  u, v = map(int, input().split())
  if dp[u][v] == INF:
    print(-1)
  else:
    print(dp[u][v])

蓝桥8336

import os
import sys

# 请在此输入您的代码
"""
翻译题意:有n个城市,m条边就是有m条路径可以流通,每个城市有自己的商品产出,可以拿去别的地方销售,
需要求出最大利润,但是商品产量ai是不变的,生产成本pi也是不变的,只有售卖单价会随着商品运输到其他
城市会改变,以及带来的运输费用(这里的运输费用有一个路径的问题,需要用最短路算出来最少支付。
"""

n, m = map(int, input().split())
# g = s - p - f(路径费用)
INF = 10 ** 17
a, p, s = [0] * (n + 1), [0] * (n + 1), [0] * (n + 1) # 商品的产量, 生产成本, 售卖单价
f = [[INF] * (n + 1) for i in range(n + 1)] # 记录最短路(也就是最短的运输费用)
g = [[0] * (n + 1) for i in range(n + 1)] # 记录利润
for i in range(1, n + 1):
  a[i], p[i], s[i] = map(int, input().split())
for _ in range(1, m + 1):
  u, v, w = map(int, input().split())
  f[u][v] = f[v][u] = min(f[u][v], w)
for i in range(1, n + 1):
  f[i][i] = 0

for k in range(1, n + 1):
  for i in range(1, n + 1):
    for j in range(1, n + 1):
      f[i][j] = min(f[i][k] + f[k][j], f[i][j])
# g[i][j]表示城市1的物品运输到城市j可得的利润=城市j的售价-城市i的成本-运输f[i][j]
for i in range(1, n + 1):
  for j in range(1, n + 1):
    g[i][j] = s[j] - p[i] - f[i][j]
ans = 0
for i in range(1, n + 1):
  # 遍历每个城市的商品
  now_ans = 0
  # 遍历移动到的城市(包括自己本身)
  for j in range(1, n + 1):
    now_ans = max(now_ans, a[i] * g[i][j])
  ans += now_ans # 记录每个城市的利润

print(ans)

蓝桥4218

import os
import sys

'''
floyd算法小变形,题目的意思是两个人要先找到一个地方会面,之后再各自前往各自的目的地.
所以这个会面的时间是由晚到的那个人决定的,(同时从这个时间开始出发)
最终最早的最晚到达时间也是由最晚的那个人决定的
这就是为什么选取两个最大值求和的答案再跟原来的答案作比较,取最小值.
ans = min(ans,max(dp[s1][i],dp[s2][i])+max(dp[i][e1],dp[i][e2]))
'''

# 请在此输入您的代码
n, m = map(int, input().split())
G = [[] * (n + 1) for _ in range(n + 1)]
inf = 10 ** 18
dp = [[inf] * (n + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
  dp[i][i] = 0
for _ in range(m):
  a, b, t = map(int, input().split())
  dp[a][b] = dp[b][a] = min(dp[a][b], t)
s1, e1, s2, e2 = map(int, input().split())

for k in range(1, n + 1):
  for i in range(1, n + 1):
    for j in range(1, n + 1):
      dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j])

ans = inf
for i in range(1,n+1):
    ans = min(ans,max(dp[s1][i],dp[s2][i])+max(dp[i][e1],dp[i][e2]))
if ans==inf:
    print(-1)
else:
    print(ans)

总结下解题步骤:

  1. 初始化邻接矩阵(有边直接连接的直接存,没有的存INF最大值,自己到自己的路径长度为0)
  2. 遍历(k,i,j)更新i到j的最短路,通过k
  3. 依据题意更新答案

2. Dijkstra算法

作用:处理非负权边单源最短路问题

利用贪心+动态规划思想,实现从源点s出发到所有点的最短距离

核心思想:从起点出发,每次选择距离最短的点进行”松弛”操作

算法步骤:

1.将起点入队列,d数组表示从起点s出发到达每个最短距离

2.不断取出队列中距离最小的点u,进行“松弛”:

对于从u到v,权重为w的边

dp[v] = min(dp[v], dp[u] + w)

例题:

蓝桥1122

import os
import sys

# 请在此输入您的代码
from queue import PriorityQueue # 导入优先队列(出队列先出最短的)
INF = 10 ** 18
def dijkstra(s):
  # 返回从s到所有点的最短路
    d = [INF] * (n + 1)
    vis = [0] * (n + 1) # 只有第一次出队列是有用的,这里与BFS不同,是标记出队列
    q = PriorityQueue()
    # 1. 将起点入队列,更新距离
    d[s] = 0
    q.put((d[s], s))
    # 2. 当队列非空
    while not q.empty():
        dis, u = q.get()
        if vis[u]:  
            continue
        vis[u] = 1
        # 对于从u出发,到达v,权重为w的边
        for v, w in G[u]:
            # 松弛
            if d[v] > d[u] + w:
                d[v] = d[u] + w
                q.put((d[v], v))
    for i in range(1, n + 1):
        if d[i] == INF:
            d[i] = -1
    return d[1::]


n, m = map(int, input().split())
G = [[] for i in range(n + 1)]
for i in range(m):
    u, v, w = map(int, input().split())
    G[u].append((v, w))
print(*dijkstra(1))

蓝桥3818

"""
题目解读:
从起点坐标移动至终点坐标,经过的恶魔果实消耗能量加起来不能超过E,求一个到终点的最短路,
只不过这里的边是消耗的能量

"""
import sys
input = sys.stdin.re adline
from queue import PriorityQueue # 导入优先队列(出队列先出最短的)
INF = 10 ** 18
def get(c):
    if c == '.':
        return 0
    else:
        return ord(c) - ord('A') + 1

def dijkstra():
  # 返回从s到所有点的最短路
    d = [[INF] * m for i in range(n)]
    vis = [[0] * m for i in range(n)] # 只有第一次出队列是有用的,这里与BFS不同,是标记出队列
    q = PriorityQueue()
    # 1. 将起点入队列,更新距离
    d[x1][y1] = 0
    q.put((d[x1][y1], x1, y1))
    # 2. 当队列非空
    while not q.empty():
        dis, x, y = q.get() # 取出最小距离的点
        if x == x2 and y == y2: # 如果是终点,直接返回距离
            return dis
        if vis[x][y]:  
            continue
        vis[x][y] = 1
        # 对于从u出发,到达v,权重为w的边
        for dx, dy in [(1, 0), (0, 1), (-1, 0), (0, -1)]:
            xx, yy = x + dx, y + dy
            # 判断越界和标记,障碍物
            if 0 <= xx < n and 0 <= yy < m and vis[xx][yy] == 0 and Map[xx][yy] != '#':
                if d[xx][yy] > d[x][y] + get(Map[xx][yy]):
                    d[xx][yy] = d[x][y] + get(Map[xx][yy])
                    q.put((d[xx][yy], xx, yy))
    return INF

n, m = map(int, input().split())
x1, y1, x2, y2 = map(int, input().split())
x1, y1, x2, y2 = x1 - 1, y1 - 1, x2 - 1, y2 - 1
Map = [input() for i in range(n)]
E = int(input())
if E >= dijkstra():
    print("Yes")
else:
    print("No")
    

正在更新中...

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值