蓝桥杯备赛(三)— 最短路

抱佛脚Day03

最短路问题是图论中最基础的问题,在程序设计竞赛试题中也经常出现。最短路是给定两个顶点,在以这两个点为起点和终点的路径中,边的权值和最小的路径。如果把权值当作距离,考虑最短距离的话就很容易理解了。智力游戏中的求解最少步数问题也可以说是一种最短路问题。

1.单源最短路问题 (Bellman-Ford算法)

单源最短路问题是固定一个起点,求它到其他所有点的最短路的问题。终点也固定的问题叫做两点之间最短路问题。但是因为解决单源最短路问题的复杂度也是一样的,因此通常当作单源最短路问题来求解。

记从起点 s s s出发到顶点 i i i的最短距离为 d [ i ] d[i] d[i]。则下述等式成立。

  • d [ i ] = min ⁡ ( d [ j ] + v a l ( j , i ) ∣ e ( j , i ) ∈ E ) d[i] = \min(d[j] + val(j, i)|e(j, i)\in \mathbb E) d[i]=min(d[j]+val(j,i)e(j,i)E)

如果给定的图是一个DAG,就可以按拓扑序给顶点编号,并利用用这条递推关系式计算出 d d d。但是,如果图中有圈,就无法依赖这样的顺序进行计算。在这种情况下,记当前到顶点 i i i最短路长度为 d [ i ] d[i] d[i],并设初值 d [ s ] = 0 , d [ i ] = i n f d[s]=0,d[i]=inf d[s]=0,d[i]=inf,再不断使用这条递推关系式更新 d d d的值,就可以算出新的 d d d。只要图中不存在负圈,这样的更新操作就是有限的。结束之后的 d d d是所求的最短距离了。

  • 时间复杂度: O ( ∣ V ∣ × ∣ E ∣ ) O(|V|\times|E|) O(V×E)

例一

1
2
3
1
A
B
C
D

d [ A ] = 0 , d [ B ] = d [ C ] = d [ D ] = i n f d[A] = 0,d[B] = d[C] = d[D] = inf d[A]=0,d[B]=d[C]=d[D]=inf

d [ B ] = d [ A ] + 1 = 1 , d [ C ] = d [ A ] + 2 = 2 d[B] = d[A] + 1 = 1,d[C] = d[A] + 2 = 2 d[B]=d[A]+1=1,d[C]=d[A]+2=2

d [ D ] = min ⁡ ( d [ B ] + 3 , d [ C ] + 1 ) = 3 d[D] = \min(d[B] + 3, d[C] + 1) = 3 d[D]=min(d[B]+3,d[C]+1)=3

numsV = 4
numsE = 4
edges = [edge(0, 1, 1),
         edge(0, 2, 2),
         edge(1, 3, 3),
         edge(2, 3, 1)]

例二

1
-2
A
B
class edge(object):
    def __init__(self, start, end, cost):
        self.start = start
        self.end = end
        self.cost = cost

def shortest_path(start, numsV, numsE):
    d = [float("inf")] * numsV
    d[start] = 0
    while True:
        update = False
        for i in range(numsE):
            e = edges[i]
            if d[e.start] != float("inf") and d[e.end] > d[e.start] + e.cost:
                d[e.end] = d[e.start] + e.cost
                update = True
        if update == False:
            break
    return d

def find_negative_loop(numsV, numsE):
    d = [0] * numsV
    for i in range(numsV):
        for j in range(numsE):
            e = edges[j]
            if d[e.end] > d[e.start] + e.cost:
                d[e.end] = d[e.start] + e.cost
                if i == numsV - 1:
                    return True
    return False

if __name__ == "__main__":
    numsV = 2
    numsE = 2
    edges = [edge(0, 1, 1), edge(1, 0, 2)]
    if find_negative_loop(numsV, numsE) == False:
        d = shortest_path(0, numsV, numsE)
        print(d)
    else:
        print("Exist negetive loop!")

2.单源最短路问题 (Dijkstra算法)

让我们考虑一下没有负边的情况。在 B e l l m a n − F o r d Bellman-Ford BellmanFord算法中,如果 d [ i ] d[i] d[i]还不是最短距离的话,那么即使进行 d [ j ] = d [ i ] + v a l ( j , i ) d[j]=d[i]+val(j,i) d[j]=d[i]+val(j,i)的更新, d [ j ] d[j] d[j]也不会变成最短距离。而且,即使 d [ i ] d[i] d[i]没有变化,每一次循环也要检査一遍从 i i i出发的所有边。这显然是很浪费时间的。因此可以对算法做如下修改。

  • 找到最短距离已经确定的顶点,从它出发更新相邻顶点的最短距离。
  • 此后不需要再关心上一条中的“最短距离已经确定的顶点”。
1
2
3
1
A
B
C
D

写法一:枚举所有的顶点来查找下一个使用的顶点

class edge(object):
    def __init__(self, start, end, cost):
        self.start = start
        self.end = end
        self.cost = cost

def dijkstraInit(numsV, edges):
    cost = [[float("inf")] * numsV for _ in range(numsV)]
    for edge in edges:
        cost[edge.start][edge.end] = edge.cost
        cost[edge.end][edge.start] = edge.cost
    d = [float("inf")] * numsV
    used = [False] * numsV
    return cost, d, used

if __name__ == "__main__":
    numsV = 4
    numsE = 4
    edges = [edge(0, 1, 1),
             edge(0, 2, 2),
             edge(1, 3, 3),
             edge(2, 3, 1)]
    cost, d, used = dijkstraInit(numsV, edges)
    d[0] = 0
    while True:
        v = -1
        for u in range(numsV):
            if not used[u] and (d[u] < d[v] or v == -1):
               v = u
        if v == -1: # 顶点都使用过了
            break
        used[v] = True
        for u in range(numsV):
            d[u] = min(d[u], d[v] + cost[v][u])
    print(d)

写法二:堆

import heapq
import collections

class edge(object):
    def __init__(self, start, end, cost):
        self.start = start
        self.end = end
        self.cost = cost

def dijkstraInit(numsV, start, edges):
    graph = collections.defaultdict(list)
    for edge in edges:
        graph[edge.start].append([edge.end, edge.cost])
        graph[edge.end].append([edge.start, edge.cost])
    que = []
    d = [float("inf")] * numsV
    d[start] = 0
    heapq.heappush(que, [0, start])
    return graph, que, d

if __name__ == "__main__":
    numsV = 4
    numsE = 4
    edges = [edge(0, 1, 1),
             edge(0, 2, 2),
             edge(1, 3, 3),
             edge(2, 3, 1)]
    graph, que, d = dijkstraInit(numsV, 1, edges)
    
    while len(que) != 0:
        item = heapq.heappop(que)
        v = item[1]
        if d[v] < item[0]:
            continue
        for i in range(len(graph[v])):
            e = graph[v][i]
            if d[e[0]] > d[v] + e[1]:
                d[e[0]] = d[v] + e[1]
                heapq.heappush(que, [d[e[0]], e[0]])
    print(d)  

3.任意两点间的最短路问题(Floyd-Warshall算法)

求解所有两点间的最短路的问题叫做任意两点间的最短路问题。让我们试着用DP来求解任意两点间的最短路问题。

import heapq
import collections

class edge(object):
    def __init__(self, start, end, cost):
        self.start = start
        self.end = end
        self.cost = cost

if __name__ == "__main__":
    numsV = 4
    numsE = 4
    edges = [edge(0, 1, 1),
             edge(0, 2, 2),
             edge(1, 3, 3),
             edge(2, 3, 1)]
    d = [[float("inf")] * numsV for _ in range(numsV)]
    for i in range(numsV):
        d[i][i] = 0
    for edge in edges:
        d[edge.start][edge.end] = edge.cost
        d[edge.end][edge.start] = edge.cost

    for k in range(numsV):
        for i in range(numsV):
            for j in range(numsV):
                d[i][j] = min(d[i][j], d[i][k] + d[k][j])
    print(d)

4.路径还原

截至目前,我们都只是在求解最短距离。虽然许多问题只需输出最短距离就可以了,但是也有的问题需要求解最短路的路径。我们以Dijkstra算法为例,试着来求解最短路径。在求解最短距离
时,满足 d [ j ] = d [ k ] + c o s t [ k ] [ j ] d[j] = d[k] + cost[k][j] d[j]=d[k]+cost[k][j]的顶点k, 就是最短路上顶点j的前趋节点,因此通过不断寻找前趋节点就可以恢复出最短路。时间度杂度是 O ( ∣ E ∣ ) O(|E|) O(E),如果用prev[j]来记录最短路上顶点j的前趋,那么就可以在 O ( ∣ V ∣ ) O(|V|) O(V)的时间内完成最短路的恢复。

def get_path(t):
	path = []
    while t != -1:
        path.append(t)
        t = prev[t]
    return path.reverse()
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值