单源最短路径和多源最短路径的四种算法及使用场景(Floyed + Dijkstra + Bellman-Ford + SPFA)---附Python实现实例

算法原理

        四种算法原理请参考B站UP主麦克老师讲算法

题目——蓝桥王国:

小明是蓝桥王国的王子,今天是他登基之日。
在即将成为国王之前,老国王给他出了道题,他想要考验小明是否有能力管理国家。
题目的内容如下:
蓝桥王国一共有 NN 个建筑和 MM 条单向道路,每条道路都连接着两个建筑,每个建筑都有自己编号,分别为 1∼N1∼N 。(其中皇宫的编号为 11)
国王想让小明回答从皇宫到每个建筑的最短路径是多少,但紧张的小明此时已经无法思考,请你编写程序帮助小明回答国王的考核。
输入描述
输入第一行包含三个正整数 N,MN,M。
第 22 到 M+1M+1 行每行包含三个正整数 u,v,wu,v,w,表示 u→vu→v 之间存在一条距离为 ww 的路。
1≤N≤3×1051≤N≤3×105,1≤m≤1061≤m≤106,1≤ui,vi≤N1≤ui​,vi​≤N,0≤wi≤1090≤wi​≤109。
输出描述
输出仅一行,共 NN 个数,分别表示从皇宫到编号为 1∼N1∼N 建筑的最短距离,两两之间用空格隔开。(如果无法到达则输出 −1−1)
3 3
1 2 1
1 3 5
2 3 2

0 1 3

Floyed算法

当问题要求任意两点之间的距离的话使用Floyed算法

n, m = map(int, input().split())

# 初始化INF的值
INF = float('inf')

# 初始化邻接矩阵
dp = [[INF for _ in range(n+1)] for _ in range(n+1)]
for _ in range(m):
    u, v, w = map(int, input().split())
    # 这里需要取最小,防止有两节点间有重复路径
    dp[u][v] = min(dp[u][v], w)

def Floyed():
    # 枚举所有结点(让其他节点试图通过该节点找到所更短路径)
    for i in range(1, n+1):
        dp[i][i] = 0
        for u in range(1, n+1):
            for v in range(1, n+1):
                dp[u][v] = min(dp[u][v], dp[u][i] + dp[i][v])

Floyed()

print(dp)
for i in dp[1][1:]:
    print(i, end=' ')

Dijkstra算法

当题目要求单源最短路径时,当各个边的权值都为正值的话,优先使用Dijkstra算法,Dijkstra算法的效率要优于Bellman-Ford和SPFA。

import heapq
n, m = map(int, input().split())

# 初始化INF的值
INF = float('inf')
# 初始化记录路径值的列表
dis = [INF for _ in range(n+1)]
dis[1] = 0
# 初始化记录最短路径的列表
pre = [0 for _ in range(n+1)]
# 初始化记录路径的列表
vis = [0 for _ in range(n+1)]
# 创建一个记录最小值的小根堆(模拟优先队列)
path = []
heapq.heappush(path, [0, 1])

# 创建保存路径的列表
a = [[] for _ in range(n+1)]
for _ in range(m):
    u, v, w = map(int, input().split())
    a[u].append([v, w])

while path:
    # 取出距离最小值的结点
    mine = heapq.heappop(path)[1]
    if vis[mine] == 1:
        continue
    vis[mine] = 1
    # 寻找最小距离的点的可达点
    for v, w in a[mine]:
        if not vis[v] and dis[v] > dis[mine]+w:
            dis[v] = dis[mine] + w
            pre[v] = mine
            heapq.heappush(path, [dis[v], v])

def print_path(i):
    if i == 0:
        return
    print_path(pre[i])
    print(i, end='->')


for i in range(1, n+1):
    print('距离点{}: '.format(i), dis[i])
    print("路径为:", end=' ')
    print_path(i)

Bellman-Ford算法

Bellman-Ford算法适用于解决单源最短路径中出现边的权值为负值的情况,但是当给出的路径中出现一个权值和为负值的环的情况下,该算法不能得到正确答案。另外该算法也可以用来判断路径中是否有权值和为负值的环,当双重循环结束后,理应得到所有点距目标点最近的距离,但是由于权值和为负值的环的出现,接着进行循环时有些点的最近距离会继续更新,显然这是不合理的。

n, m = map(int, input().split())

# 初始化INF的值
INF = float('inf')
# 初始化记录路径值的列表
dis = [INF for _ in range(n+1)]
dis[1] = 0
# 初始化记录最短路径的列表
pre = [0 for _ in range(n+1)]
# 初始化记录边的列表
path = [list(map(int, input().split())) for _ in range(m)]
def print_path(i):
    if i == 0:
        return
    print_path(pre[i])
    print(i, end='->')
def Bellman_Ford():
    for _ in range(n-1):
        for u, v, w in path:
            if dis[u] + w < dis[v]:
                pre[v] = u
                dis[v] = w + dis[u]
    # 判断是否有负权值环
    for u, v, w in path:
        if dis[v] > dis[u]+w:
            print("存在负权值环")
            raise EOFError("请确保不存在权值和为负值的环")
Bellman_Ford()
for i in range(1, n+1):
    print()
    print('距离点{}: '.format(i), dis[i])
    print("路径为:", end=' ')
    print_path(i)
print()

SPFA算法

该算法是Bellman-Ford算法改进算法,避免了很多冗余的计算,用的类似广度优先搜索的方式对最近距离进行迭代。适用场景与Bellman-Ford算法一样,同样它也能用来判断路径中是否有权值和为负值的环。

import collections
# 类似与广度优先搜索
n, m = map(int, input().split())

# 初始化INF的值
INF = float('inf')
# 初始化记录路径值的列表
dis = [INF for _ in range(n+1)]
dis[1] = 0
# 初始化记录最短路径的列表
pre = [0 for _ in range(n+1)]

# 初始化记录边的列表
path = [[] for _ in range(n+1)]
for _ in range(m):
    u, v, w = map(int, input().split())
    path[u].append([v, w])
# 输出路径的函数
def print_path(i):
    if i == 0:
        return
    print_path(pre[i])
    print(i, end='->')

def SPEA():
    a = collections.deque([])
    a.appendleft(1)
    while a:
        p = a.popleft()
        for v, w in path[p]:
            if dis[p] + w < dis[v]:
                dis[v] = dis[p] + w
                pre[v] = p
                a.append(v)
SPEA()
for i in range(1, n+1):
    print()
    print('距离点{}: '.format(i), dis[i])
    print("路径为:", end=' ')
    print_path(i)
print()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Rosen。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值