一.迪杰斯特拉算法的由来(背后的故事)

上面这位"帅哥"就是荷兰计算机科学家 艾兹赫尔・W・迪杰斯特拉(Edsger W. Dijkstra),他在 1956 年发明迪杰斯特拉算法。
背后还有一段有趣的小插曲:当时迪杰斯特拉和妻子在阿姆斯特丹逛街,妻子问他 "从当前位置到市中心的所有路线中,哪条最短?",这个日常问题激发了他的思考。他当时正为 "如何高效找到图中两点间最短路径" 的问题发愁,这次闲聊让他茅塞顿开,回家后仅用 20 分钟就构思出了这个算法的核心逻辑。
迪杰斯特拉本人是结构化编程的先驱,他的算法核心思想是 "贪心策略"------ 每次选择当前距离起点最近的未访问节点,逐步扩展到所有节点,最终得到起点到所有节点的最短路径。这个算法最初是为了解决 "从鹿特丹到格罗宁根的最短路线" 这类实际交通问题,后来成为图论中最经典的最短路径算法之一。
二、迪杰斯特拉算法的核心作用
简单来说,迪杰斯特拉算法的核心作用是:在一个带权(权重为非负数)的有向 / 无向图中,找到从一个指定起点到其他所有节点的最短路径。它的典型应用场景包括:
- 导航软件(如高德、百度地图)计算两点间最短 / 最快路线;
- 网络路由协议(如 OSPF)选择最优数据传输路径;
- 物流配送规划(如外卖、快递的最优配送路线);
- 游戏中角色移动的最短路径计算。
三、迪杰斯特拉算法的具体可执行代码
下面提供基于 邻接表 的 Python 实现(邻接表是图的高效存储方式,适合稀疏图),代码包含详细注释,可直接复制运行。
代码实现(优先队列优化版,时间复杂度 O (E log V))
python
import heapq
def dijkstra(graph, start):
"""
迪杰斯特拉算法实现(优先队列优化)
:param graph: 邻接表表示的图,格式为 {节点: [(邻居节点, 边权重), ...]}
:param start: 起点节点
:return: 起点到各节点的最短距离字典,前驱节点字典(用于回溯路径)
"""
# 1. 初始化:所有节点的最短距离设为无穷大,起点设为0
INF = float('inf')
shortest_dist = {node: INF for node in graph}
shortest_dist[start] = 0
# 前驱节点字典,用于回溯最短路径
predecessor = {node: None for node in graph}
# 2. 优先队列(小顶堆),存储 (当前距离, 节点),初始放入起点
# heapq默认是小顶堆,保证每次取出距离最小的节点
priority_queue = []
heapq.heappush(priority_queue, (0, start))
# 3. 记录已访问的节点(避免重复处理)
visited = set()
# 4. 核心循环:处理优先队列中的节点
while priority_queue:
# 取出当前距离最小的节点
current_dist, current_node = heapq.heappop(priority_queue)
# 如果该节点已访问,跳过(避免重复处理)
if current_node in visited:
continue
# 标记为已访问
visited.add(current_node)
# 遍历当前节点的所有邻居
for neighbor, weight in graph[current_node]:
# 跳过已访问的邻居(优化)
if neighbor in visited:
continue
# 计算从起点到邻居的临时距离
temp_dist = current_dist + weight
# 如果临时距离比已知的最短距离更小,更新
if temp_dist < shortest_dist[neighbor]:
shortest_dist[neighbor] = temp_dist
predecessor[neighbor] = current_node # 更新前驱节点
# 将更新后的距离和邻居节点加入优先队列
heapq.heappush(priority_queue, (temp_dist, neighbor))
return shortest_dist, predecessor
# 辅助函数:根据前驱节点字典,回溯从起点到目标节点的最短路径
def get_shortest_path(predecessor, start, target):
"""
回溯最短路径
:param predecessor: 前驱节点字典
:param start: 起点
:param target: 目标节点
:return: 最短路径列表(如 [A, B, C])
"""
path = []
current = target
# 从目标节点往回找,直到起点
while current is not None:
path.append(current)
current = predecessor[current]
# 防止循环(如果目标节点不可达)
if len(path) > len(predecessor):
return []
# 反转路径,得到从起点到目标的顺序
path.reverse()
# 如果路径的第一个节点不是起点,说明不可达
return path if path[0] == start else []
# ------------------- 测试代码 -------------------
if __name__ == "__main__":
# 构建示例图(邻接表)
# 节点:A, B, C, D, E
# 边:A->B(2), A->C(5), B->C(1), B->D(3), C->D(2), C->E(4), D->E(1)
graph = {
'A': [('B', 2), ('C', 5)],
'B': [('C', 1), ('D', 3)],
'C': [('D', 2), ('E', 4)],
'D': [('E', 1)],
'E': []
}
# 执行迪杰斯特拉算法,起点为A
start_node = 'A'
shortest_dist, predecessor = dijkstra(graph, start_node)
# 输出起点到各节点的最短距离
print(f"起点 {start_node} 到各节点的最短距离:")
for node, dist in shortest_dist.items():
print(f"{start_node} -> {node}: {dist}")
# 输出从A到E的最短路径
target_node = 'E'
path = get_shortest_path(predecessor, start_node, target_node)
print(f"\n从 {start_node} 到 {target_node} 的最短路径:{' -> '.join(path)}")
代码运行结果
起点 A 到各节点的最短距离:
A -> A: 0
A -> B: 2
A -> C: 3
A -> D: 5
A -> E: 6
从 A 到 E 的最短路径:A -> B -> C -> D -> E
迪杰斯特拉算法,重要的点在于距现在的位置最短、未访问。A分别到B、C、D、E的距离逐渐增大,所以先找最小的。
代码关键部分解释
- 邻接表 :
graph字典是图的核心存储方式,键是节点,值是该节点的邻居和边权重的列表,适合稀疏图(大部分节点无连接)。 - 优先队列(小顶堆) :
heapq模块实现,保证每次取出当前距离起点最近的节点,这是迪杰斯特拉 "贪心" 思想的核心。 - 已访问集合:避免重复处理同一节点,减少无效计算。
- 前驱节点字典:用于回溯最短路径,比如从 E 往回找 D→C→B→A,反转后得到完整路径。
四、总结
- 由来:由荷兰科学家迪杰斯特拉受日常问路启发发明,核心是 "贪心策略",解决带权无负边图的最短路径问题。
- 作用:找到图中起点到所有节点的最短路径,广泛应用于导航、路由、物流等场景(仅支持非负权边)。
- 代码核心:用优先队列(小顶堆)优化,每次选距离最小的节点扩展,更新邻居的最短距离,最终得到所有节点的最短路径。