三叶姐关于图与路径规划的文章:三叶姐图的文章
Dijkstra
简单版的Djikstra算法(不使用小根堆)的算法并不难,本质上就是贪心,每一次都取距离出发点最近的点取它最近的边进行遍历,下面是 leetcode M743的解法
class Solution:
def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int:
# 使用邻接矩阵建图
g = [[float('inf') for _ in range(n+1)] for _ in range(n+1)]
for u,v,w in times:
g[u][v]=w
# d[i]是初始节点k到节点i的距离,初始都是 inf
d = [float('inf') for _ in range(n+1)]
d[k] = 0 # 节点k到自己的距离是0
# Dijkstra,传入图、距离列表和开始位置,运行之后函数外面的d就会被改变
def Dijkstra(g,d,k):
# 判断点i是否已经计算出了最短路径
used = set()
# 一共有n个节点,所以需要寻找n次
for _ in range(n):
min_d = float('inf')
# 找到距离开始节点最近而且不在used中的节点
# 假设最近的距离是x,节点是node
x = float('inf')
node = -1
# 这里使用遍历的方式找到最小值
for i in range(1,n+1):
if (i not in used) and (node==-1 or d[i]<=x):
x = d[i]
node=i
# 将找到的这个节点添加进使用过的集合表示这个点已经找到最短路径了
used.add(node)
for i in range(1,n+1):
d[i] = min(d[i], d[node]+g[node][i])
return
Dijkstra(g,d,k)
ans = max(d[1:]) # 注意排除d[0],因为0并不是节点
return ans if ans<float('inf') else -1
使用小根堆稍微优化的Dijkstra算法,还是解上面的题:
import heapq
class Solution:
def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int:
# 使用邻接矩阵建图
g = [[float('inf') for _ in range(n+1)] for _ in range(n+1)]
for u,v,w in times:
g[u][v]=w
# Dijkstra,传入图、开始位置
def Dijkstra(g,k):
# d[i]使用小根堆的话还是需要一个东西来记录初始节点到这个节点的距离,默认是正无穷
dist = defaultdict(lambda:float('inf'))
# d是小根堆
d = [[0, k]] # [dis,j] 表示出发节点到节点j的距离为dis,初始时只有出发节点自己以及他到自己的距离0
# 由于这一题的特殊性,需要记录所有的最短路径
al_dis = [] # al_dis记录的是初始节点到达每一个节点的最短距离
# 只要小根堆中还有节点就要pop
while d:
# 找到目前能找到的距离开始节点最近的节点
dis,node = heapq.heappop(d)
# 如果这个节点之前已经有距离记录在案,而且目前这个距离比他还大就不用管了,直接跳过
# 这样就可以省略掉判断节点node是否已经被遍历过这一步,反正直接跳过
# 但是如果相等就不能跳过,否则图就已经结束了
if dist[node]<dis:
continue
else:
dist[node]=dis
al_dis.append(dis)
# 将node能到达的且不在used中的节点push进距离列表d中
for ind, i in enumerate(g[node]):
# 遍历的时候ind就是节点编号!,i是这个节点距离node的距离
if (d_:=dis+i)<dist[ind]:
# 注意push进去的是经过node到节点ind的距离
dist[ind]=d_
heapq.heappush(d, [d_,ind])
return al_dis
al_dis = Dijkstra(g,k)
if len(al_dis)<n:
return -1
ans = max(al_dis)
return ans if ans<float('inf') else -1
总结一下,使用小根堆的Dijkstra算法,用于在一个带非负权重的有向图中,计算从出发点到图中任意一点的最短距离(以及整个路径),使用该算法的过程中需要使用邻接矩阵表示整个图的结构,使用一个小根堆实时找到目前从出发点出发能到达的而且还没有记录在案的最近节点以及这个距离,使用一个字典记录已经找到最短距离的节点以及这个最短距离,使用一个字典记录从开始结点出发通过最短路径到达节点i时在到达i之间到达的最后一个节点(用来反推整个路径)
import heapq
from collections import defaultdict
# 一共n个节点,每一条边的开始、结束、权重都放在lines中
# 注意我使用的邻接矩阵默认节点编号从1开始,不减一
def generate_graph(n, lines):
g = [[float('inf') for _ in range(n + 1)] for _ in range(n + 1)]
for s, e, w in lines:
g[s][e] = w
return g
# 传进来使用邻接矩阵表示的图以及出发节点,对于Dijkstra算法而言出发点是固定不变的
def Dijkstra(g, start):
dist = defaultdict(lambda: float('inf')) # 凡是记录在案的节点都是已经找到最短路径的节点
previous = defaultdict(lambda: start) # 凡是记录在案的节点也都是已经找到最短路径的节点
d = [[0, start, -1]] # 小根堆用于快速找到该pop的节点
while d:
dis, node, pre = heapq.heappop(d)
if dist[node] <= dis: # 如果上次进堆的距离比之前找到的距离还远直接continue
continue
dist[node] = dis
previous[node] = pre
for ind, i in enumerate(g[node]):
if (d_ := dis + i) < dist[ind]:
heapq.heappush(d, [d_, ind, node])
return previous