【模板题】单源最短路径一只棠子的dijkstra,启动!
背景介绍
Dijkstra算法是由荷兰计算机科学家Edsger W. Dijkstra在1956年提出的一种用于计算图中单源最短路径的算法。它以贪心策略为基础,通过逐步扩展已知最短路径的顶点集合,找到从起始点到所有其他顶点的最短路径。
Dijkstra算法的基本原理
Dijkstra算法的基本思想是通过逐步选择距离起始点最近的未访问顶点,更新其邻接顶点的最短路径,直到所有顶点都被访问到。具体步骤如下:
-
初始化:
- 设定起始顶点的距离为0,其余顶点的距离为正无穷大。
- 使用一个优先队列(通常用最小堆实现)来存储每个顶点及其当前已知的最短距离。
- 设定一个已访问顶点的集合,初始为空。
-
迭代更新:
- 每次从优先队列中取出距离最小的顶点作为当前顶点,并标记为已访问。
- 更新当前顶点的所有邻接顶点的最短距离,如果通过当前顶点到某个邻接顶点的路径比已知的路径更短,则更新该邻接顶点的最短距离,并将其加入优先队列。
- 重复上述步骤,直到优先队列为空。
算法时间复杂度
Dijkstra算法的时间复杂度主要取决于图的表示方式和优先队列的实现方式:
- 使用邻接矩阵和简单数组实现优先队列时,时间复杂度为O(V^2),其中V是顶点数。
- 使用邻接表和二叉堆实现优先队列时,时间复杂度为O((V + E) log V),其中E是边数。
Dijkstra算法的优缺点
优点:
- 适用于边权非负的图。
- 算法简单易于实现。
- 通过使用优先队列,算法在稀疏图中表现优异。
缺点:
- 不适用于存在负权边的图(负权边可能导致算法陷入无限循环)。
- 在某些情况下,可能不如其他最短路径算法(如Floyd-Warshall算法)高效。
话不多说,上模板题!
这是一个很经典的堆优化的dijkstra😎
根据原理开始套!
一:初始化
dis = {i: float('inf') for i in graph}
dis[start] = 0
queue = [(0, start)]
-
初始化距离字典dis = {i: float('inf') for i in graph}
dis
,所有节点的初始距离设为正无穷大。 -
将起点dis[start] = 0
start
的距离设为0,即所求点到本身的距离为0。 -
将起点queue = [(0, start)]
start
和当前花费0以元组形式加入优先队列queue
。
二:迭代更新
while queue: # 队列不为空
cost, node = heappop(queue) # 从队列中弹出花费最小的节点
if cost > dis[node]:
continue
for neighbor, edge in graph[node]: # 遍历当前节点的邻居节点及对应的边权重
distance = cost + edge
if distance < dis[neighbor]:
dis[neighbor] = distance
heappush(queue, (distance, neighbor)) # 将邻居节点及从起点到达邻居节点的新花费加入队列
return dis
-
当优先队列不为空时,继续循环。while queue:
-
从优先队列中弹出花费最小的节点及其对应的花费。cost, node = heappop(queue)
-
如果当前节点的花费大于已知最小花费,说明是过期数据,跳过。if cost > dis[node]:
-
遍历当前节点的所有邻居节点及对应的边权重。for neighbor, edge in graph[node]:
-
如果新的花费小于已知最小花费,则更新。if distance < dis[neighbor]:
-
更新邻居节点的最小花费。dis[neighbor] = distance
-
将邻居节点及新的花费加入优先队列。heappush(queue, (distance, neighbor))
-
返回从起点到所有节点的最小花费。return dis
三:输入输出
n, m , s= map(int, input().split())# 读取输入
graph = {i: [] for i in range(1, n + 1)} # 创建一个空图,用字典表示,键为节点编号,值为与该节点相连的边及权重
for i in range(m):
u, v, w = map(int, input().split())
graph[u].append((v, w))
result = dijkstra(graph, s) #s到每个点的距离
print(*result.values())
graph = {i: [] for i in range(1, n + 1)}
- 创建图,用字典表示,键为节点编号,值为与该节点相连的边及权重。
graph[u].append((v, w))
- 将边的信息加入图的邻接表中。
以下是完整代码,嘿咻~
from heapq import heappop, heappush
def dijkstra(graph, start):
dis = {i: float('inf') for i in graph} # 初始化距离字典,起点距离为0,其它点距离为无穷大
dis[start] = 0
queue = [(0, start)] # 起点start和当前花费0
while queue: # 队列不为空
cost, node = heappop(queue) # 从队列中弹出花费最小的节点
if cost > dis[node]: # 如果取出的距离比已知的距离大,说明是旧数据,忽略
continue
for neighbor, edge in graph[node]: # 遍历当前节点的邻居节点及对应的边权重
distance = cost + edge
if distance < dis[neighbor]:
dis[neighbor] = distance
heappush(queue, (distance, neighbor)) # 将邻居节点及从起点到达邻居节点的新花费加入队列
return dis
n, m , s= map(int, input().split())# 读取输入
graph = {i: [] for i in range(1, n + 1)} # 创建一个空图,用字典表示,键为节点编号,值为与该节点相连的边及权重
for i in range(m):
u, v, w = map(int, input().split())
graph[u].append((v, w))
# 计算从起点1到所有节点的最短路径
result = dijkstra(graph, s)
print(*result.values())
棠子的其他板
对于不同的题目要有不同的模板,不过记得不能把思维被板子限制住嘞!
一:两点最短路
(是最初的板子,不是很常用)
from heapq import heappop,heappush
def dijkstra(graph,start,end):
queue=[(0,start)]#起点start和当前花费0
visited=set()#集合,记录已经访问过的节点
while queue:#队列不为空
cost,node=heappop(queue)#从队列中弹出花费最小的节点
if node==end:#终点
return cost
if node not in visited:
visited.add(node)#标记当前节点
for neighbor , edge in graph[node]:#遍历当前节点的邻居节点及对应的边权重。
if neighbor not in visited:# 如果邻居节点没有被访问过,则将其加入队列中以便后续处理。
heappush(queue,(cost+edge,neighbor))#将邻居节点及从起点到达邻居节点的新花费加入队列。
return -1#如果没有找到从起点到终点的路径
n,m=map(int,input().split())
graph={i:[] for i in range(1,n+1)}#创建一个空图,用字典表示,键为 节点 编号,值为与该节点相连的 边 及 权重 。
for i in range(m):
x=int(input())
graph[x].append((x,x))
result=dijkstra(graph,1,n)
print(result)#输出最短路的花费
二:堆优化(一点到其他所有顶点的最短路径)
from heapq import heappop,heappush
def dijkstra(graph,start,end):
dis={i:float('inf') for i in graph} # 初始化距离字典,起点距离为0,其它点距离为无穷大
dis[start]=0
queue=[(0,start)]#起点start和当前花费0
while queue:#队列不为空
cost,node=heappop(queue)#从队列中弹出花费最小的节点
if cost >dis[node]:# 如果取出的距离比已知的距离大,说明是旧数据,忽略
continue
for neighbor , edge in graph[node]:#遍历当前节点的邻居节点及对应的边权重。
distance=cost+edge
if distance<dis[neighbor]:
dis[neighbor]=distance
heappush(queue,(distance,neighbor))#将邻居节点及从起点到达邻居节点的新花费加入队列。
return distance
n,m=map(int,input().split())
graph={i:[] for i in range(1,n+1)}#创建一个空图,用字典表示,键为 节点 编号,值为与该节点相连的 边 及 权重 。
for i in range(m):
u,v,w=map(int,input().split())
graph[u].append((v,w))
result=dijkstra(graph,1,n)
print(result)#输出最短路的花费
三:最短路径(包括经过的节点序列)
import heapq
def dijkstra(graph, start, end):
dis = {i: float('inf') for i in graph} # 初始化距离字典,起点距离为0,其它点距离为无穷大
dis[start] = 0
prnode = {i: None for i in graph} # 初始化前驱字典,用于恢复路径
queue = [(0, start)] # 起点start和当前花费0
while queue: # 队列不为空
cost, node = heapq.heappop(queue) # 从队列中弹出花费最小的节点
if cost > dis[node]: # 如果取出的距离比已知的距离大,说明是旧数据,忽略
continue
for neighbor, edge in graph[node]: # 遍历当前节点的邻居节点及对应的边权重。
distance = cost + edge
if distance < dis[neighbor]:
dis[neighbor] = distance
prnode[neighbor] = node
heapq.heappush(queue, (distance, neighbor)) # 将邻居节点及从起点到达邻居节点的新花费加入队列。
return dis, prnode
def find_shortest_path(prnode, start, target):
path = []
cnode = target
while cnode is not None:
path.append(cnode)
cnode = prnode[cnode]
return path[::-1]
n, m = map(int, input().split())
graph = {i: [] for i in range(1, n + 1)} # 创建一个空图,用字典表示,键为节点编号,值为与该节点相连的边及权重。
for i in range(m):
u, v, w = map(int, input().split())
graph[u].append((v, w))
graph[v].append((u, w)) # 因为是无向图,需要添加双向边
distances, predecessors = dijkstra(graph, 1, n)
# 输出结果
if distances[n] == float('inf'):
print(-1)
else:
path = find_shortest_path(predecessors, 1, n)
print(' '.join(map(str, path)))
加训!!!
先简单练练手
再试试思维突破!