python--蓝桥杯--最短路径

2、Bellman-Ford算法和SPFA算法、Floyd算法

        Bellman-Ford算法

        Dijkstra算法可以很好地解决无负权图的最短路径问题,但如果出现了负权边,Dijkstra算法就会失效。为了更好地求解有负权边的最短路径问题,需要使用Bellman-Ford算法(简称BF算法),和Dijkstra算法一样,Bellman-Ford算法可以解决单源最短路径问题,但也能处理有负权边的情况。Bellman-Ford算法的思路简洁直接,易于读者掌握。

        根据图中环的边权和的正负,可以将环分为零环、正环、负环。显然,图中的零环和正环不会影响最短路径的求解,而负环则不然。但是如果图中的负环无法从源点到达,则最短路径的求解不会受到影响。

 

 

## 样例输入
5 6 0 2
1 2 1 5 3
0 1 1
0 2 2
0 3 1
1 2 1
2 4 1
3 4 1
class Node:
    def __init__(self,v,dis):
        ## v为邻接边的目标顶点
        ## dis为邻接边的边权
        self.v=v
        self.dis=dis
def Bellman(s):
    global d,w,nums,pre
    ## 起点s到达自身的距离为零
    d[s]=0
    w[s]=weight[s]
    nums[s]=1
    ## 以下为求解数组d的部分
    ## 由于最短路径树的层数不会超过V层
    ## 因此Bellman-Ford算法的松弛操作不会超过V-1轮
    ## 执行n-1轮操作,n为顶点数
    for i in range(n-1):
        ## 每轮操作都遍历所有边
        for u in range(n):
            for j in range(len(Adj[u])):
                ## 邻接边的顶点
                v=Adj[u][j].v
                ## 邻接边的边权
                dis=Adj[u][j].dis
                ## 以u为中介点可以使d[v]更小
                if d[u]+dis<d[v]:
                    ## 进行松弛操作
                    d[v]=d[u]+dis
                    w[v]=w[u]+weight[v]
                    nums[v]=nums[u]
                    pre[v].clear()
                    pre[v].append(u)
                else:
                    if d[u]+dis==d[v]:
                        if w[u]+weight[v]>w[v]:
                            w[v]=w[u]+weight[v]
                        pre[v].append(u)
                        ## 重新统计nums[v]
##                        nums[v]+=nums[u]
                        nums[v]=0
                        ## Bellman-Ford算法期间会多次访问曾访问过的顶点
                        uset=set(pre[v])
                        for it in uset:
                            nums[v]+=nums[it]
    ## 判断负环的代码
    ## 对每条边进行判断
    for u in range(n):
        for i in range(len(Adj[u])):
            ## 邻接边的顶点
            v=Adj[u][j].v
            ## 邻接边的边权
            dis=Adj[u][j].dis
            ## 如果仍可以松弛
            if d[u]+dis<d[v]:
                ## 说明存在负环
                return False
    return True

n,m,s,e=map(int,input().strip().split())
## 输入点权
weight=list(map(int,input().strip().split()))
## 邻接表
Adj=[[] for _ in range(n)]
## 输入边权
for _ in range(m):
    u,v,w=map(int,input().strip().split())
    Adj[u].append(Node(v,w))
    Adj[v].append(Node(u,w))
## d存储最短路径
d=[float("inf")]*n
## w存储点权和
w=[0]*n
## nums存储最短路径数量
nums=[0]*n
## 前驱数组pre
pre=[[] for _ in range(n)]
Bellman(s)
print(nums[e])
print(w[e])
## 样例输出
2
4

        SPFA算法 

        虽然Bellman-Ford算法的思路很简洁,但是O(VE)的时间复杂度确实很高,在很多情况下并不尽人意。注意到,只有当某个顶点u的d[u]值改变时,从它出发的边的邻接点v的d[v]才可能被改变。因此可以进行一个优化:建立一个队列,每次将队首顶点u弹出,然后对从u出发的所有边u-v进行松弛操作,如果成立则进行覆盖d[v],此时如果v不在队列中,就把v加入队列。这样操作直至队列为空时(说明图中无源点可达的负环),或是某个顶点的入队次数超过V-1时(说明图中存在源点可达负环)。这种优化后的算法被称为SPFA算法,它的期望时间复杂度是O(KE),E是边数,K常是不大于2的常数。

## 样例输入
5 6 0 2
1 2 1 5 3
0 1 1
0 2 2
0 3 1
1 2 1
2 4 1
3 4 1
class Node:
    def __init__(self,v,dis):
        ## v为邻接边的目标顶点
        ## dis为邻接边的边权
        self.v=v
        self.dis=dis
from collections import deque
def SPFA(s):
    global d,inq,nums,w,num
    dq=deque()
    ## 源点入队
    dq.append(s)
    inq[s]=True
    nums[s]+=1
    d[s]=0
    w[s]=weight[s]
    num[s]=1
    while (len(dq)!=0):
        ## 队首顶点编号u
        u=dq.popleft()
        inq[u]=False
        ## 遍历u的所有邻接边v
        for j in range(len(Adj[u])):
            v=Adj[u][j].v
            dis=Adj[u][j].dis
            ## 松弛操作
            if d[u]+dis<d[v]:
                d[v]=d[u]+dis
                w[v]=w[u]+weight[v]
                num[v]=num[u]
                ## 如果v不在队列中
                if inq[v]==False :
                    dq.append(v)
                    inq[v]=True
                    nums[v]+=1
                    if nums[v]>=n:
                        ## 存在可达负环
                        return False
            else:
                if d[u]+dis==d[v]:
                    if w[u]+weight[v]>w[v]:
                        w[v]=w[u]+weight[v]
                        num[v]+=num[u]
    return True
n,m,s,e=map(int,input().strip().split())
## 输入点权
weight=list(map(int,input().strip().split()))
## 邻接表
Adj=[[] for _ in range(n)]
## 输入边权
for _ in range(m):
    u,v,w=map(int,input().strip().split())
    Adj[u].append(Node(v,w))
    Adj[v].append(Node(u,w))
## d存储最短路径
d=[float("inf")]*n
## w存储点权和
w=[0]*n
## num最短路径
num=[0]*n
## 入队次数
nums=[0]*n
##  标记是否入队
inq=[False]*n
SPFA(s)
print(num[e])
print(w[e])
## 样例输出
2
4

        Floyd算法

        Floyd算法(弗洛伊德算法)用来解决全源最短路径问题,时间复杂度O(n**3)。由于n***3的复杂度决定了顶点数n的限制约在200以内,因此使用邻接矩阵来实现Floyd算法是非常合适且方便的。

        Floyd算法基于这样一个事实:如果存在顶点k,使得以k作为中介点时顶点i和顶点j的当前距离缩短,则使用顶点k作为顶点i和顶点j的中介点。即当dis[i][k]+dis[k][j]<dis[i][j],则令dis[i][j]=dis[i][k]+dis[k][j]。

## 样例输入
6 8
0 1 1
0 3 4
0 4 4
1 3 2
2 5 1
3 2 2
3 4 3
4 5 3
def Floyd():
    for k in range(n):
        for i in range(n):
            for j in range(n):
                if dis[i][k]!=float("inf") and dis[i][k]+dis[k][j]<dis[i][j]:
                    dis[i][j]=dis[i][k]+dis[k][j]
n,m=map(int,input().strip().split())
dis=[[float("inf")]*n for _ in range(n)]
for i in range(n):
    dis[i][i]=0
for _ in range(m):
    u,v,w=map(int,input().strip().split())
    dis[u][v]=w
Floyd()
for i in range(n):
    for j in range(n):
        print(dis[i][j],end=" ")
    print()
## 样例输出
0 1 5 3 4 6 
inf 0 4 2 5 5 
inf inf 0 inf inf 1 
inf inf 2 0 3 3 
inf inf inf inf 0 3 
inf inf inf inf inf 0

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值