目录
[1. 什么是赋权最短路径](#1. 什么是赋权最短路径)
[2. 赋权最短路径中的关键概念](#2. 赋权最短路径中的关键概念)
[3. Dijkstra 算法的基本思想](#3. Dijkstra 算法的基本思想)
[4. Dijkstra 算法实现(Java)](#4. Dijkstra 算法实现(Java))
1. 什么是赋权最短路径
在图论中,最短路径问题 是指在图中寻找两点之间路径总权重最小的路径问题。如果图的每条边都带有权值(weight),这种图称为赋权图(Weighted Graph)。
2. 赋权最短路径中的关键概念
在赋权最短路径算法中,常用的几个重要概念包括:
-
顶点(Vertex/Node) 图的基本组成元素,用于表示位置或对象。
-
边(Edge) 顶点之间的连接关系,可能是有向的(Directed)或无向的(Undirected)。
-
权重(Weight) 与每条边相关联的数值,表示从一个顶点到另一个顶点的"代价",可能是时间、距离、费用等。
-
路径(Path) 由一系列顶点和边构成的通路。
-
路径长度(Path Length) 路径上所有边的权重之和。
-
前驱节点(Predecessor Node) 在最短路径中,到达某个顶点前的上一个顶点,用于回溯构建路径。
-
松弛操作(Relaxation) 如果通过某条边能找到更短的路径,则更新目标顶点的最短距离和前驱节点的过程。
3. Dijkstra 算法的基本思想
Dijkstra 算法 由 Edsger W. Dijkstra 在 1956 年提出,是解决单源非负权重最短路径问题的经典算法。 其核心思想是:
-
初始化
-
将源点的最短距离设置为 0,其他顶点设置为无穷大(
Integer.MAX_VALUE
)。 -
使用一个优先队列按当前已知的最短距离排序。
-
-
迭代选择最近的未处理顶点
-
从优先队列中取出当前距离最小的顶点
u
。 -
将其标记为"已处理",不再更新它的距离。
-
-
松弛邻居节点
-
对于
u
的所有邻接顶点v
,计算u
到v
的新距离newDist = dist[u] + weight(u, v)
。 -
如果
newDist < dist[v]
,则更新dist[v] = newDist
,并记录u
为v
的前驱节点。
-
-
重复步骤 2 和 3,直到所有顶点都处理完,或者优先队列为空。
算法特性:
-
时间复杂度:使用优先队列时为
O(E log V)
,其中E
是边数,V
是顶点数。 -
不能处理含有负权边的图(会导致错误结果)。
-
保证每次取出的顶点,其最短距离已经确定。
4. Dijkstra 算法实现(Java)
下面给出基于上述思路的完整 Java 实现代码,代码中包含了节点(Node)、边(Edge)的数据结构定义,以及 Dijkstra 算法的执行与路径回溯功能。
import java.util.*;
class Node {
public String name; // 顶点名称
public List<Edge> adj; // 邻接顶点列表
boolean processed; // 是否已处理
public int dist; // 初始距离设为无穷大
public Node preNode; // 最短路径上的前驱顶点
public Node(String name) {
this.name = name;
this.adj = new ArrayList<>();
this.processed = false;
this.dist = Integer.MAX_VALUE;
this.preNode = null;
}
// 添加邻接边
public void addEdge(Node to, int weight) {
this.adj.add(new Edge(this, to, weight));
}
}
// 边类定义
class Edge {
Node from;
Node to; // 目标节点
int weight; // 边权重
public Edge(Node from, Node to, int weight) {
this.from = from;
this.to = to;
this.weight = weight;
}
}
public class GraphShortPath {
public static final int INFINITY = Integer.MAX_VALUE;
/**
* 计算单源无权最短路径
* @param source 源顶点
*/
public static void unweighted(Node source) {
Queue<Node> queue = new LinkedList<>();
source.dist = 0; // 源点距离设为0
queue.add(source); // 源点入队
while (!queue.isEmpty()) {
Node current = queue.poll();
// 遍历所有邻接顶点
for (Edge edge : current.adj) {
Node neighbor = edge.to;
// 如果顶点未被访问过
if (neighbor.dist == INFINITY) {
neighbor.dist = current.dist + 1; // 更新距离
neighbor.preNode = current; // 记录路径
queue.add(neighbor); // 邻接点入队
}
}
}
}
/**
* Dijkstra 算法 计算 单源赋权最短路径
* @param source
*/
public static void dijkstra(Node source) {
PriorityQueue<Node> queue = new PriorityQueue<>(Comparator.comparingInt(node -> node.dist));
source.dist = 0;
queue.add(source);
while (!queue.isEmpty()) {
Node current = queue.poll();
if (current.processed) continue;
current.processed = true;
for (Edge edge : current.adj) {
Node neighbor = edge.to;
if (neighbor.processed) continue;
long newDist = (long) current.dist + edge.weight;
if (newDist > Integer.MAX_VALUE) continue;
if (newDist < neighbor.dist) { // 发现更短路径则更新
neighbor.dist = (int) newDist;
neighbor.preNode = current;
queue.add(neighbor);
}
}
}
}
/**
* 重构从源点到目标顶点的最短路径
* @param to 目标顶点
* @return 路径顶点列表
*/
public static List<Node> reconstructPath(Node to) {
List<Node> path = new ArrayList<>();
// 如果目标顶点不可达
if (to.dist == INFINITY) {
return path;
}
// 从目标顶点回溯到源顶点
for (Node v = to; v != null; v = v.preNode) {
path.add(v);
}
// 反转路径:从源顶点到目标顶点
Collections.reverse(path);
return path;
}
/**
* 打印最短路径结果
* @param vertices 顶点列表
*/
public static void printResults(List<Node> vertices) {
System.out.println("顶点\t距离\t前驱\t路径");
System.out.println("--------------------------------------");
for (Node v : vertices) {
System.out.printf("%s\t%d\t%s\t",
v.name,
v.dist,
(v.preNode != null) ? v.preNode.name : "null");
// 打印路径
List<Node> path = reconstructPath(v);
if (path.isEmpty()) {
System.out.print("不可达");
} else {
for (int i = 0; i < path.size(); i++) {
if (i > 0) System.out.print(" → ");
System.out.print(path.get(i).name);
}
}
System.out.println();
}
}
// 测试用例
public static void main(String[] args) {
// 创建顶点
Node v1 = new Node("图书馆");
Node v2 = new Node("教学楼A");
Node v3 = new Node("教学楼B");
Node v4 = new Node("食堂");
Node v5 = new Node("体育馆");
Node v6 = new Node("宿舍");
Node v7 = new Node("校门");
// 构建校园地图的无向图
v1.addEdge(v2,5);
v1.addEdge(v4,1);
v2.addEdge(v1,2);
v2.addEdge(v3,3);
v2.addEdge(v5,5);
v3.addEdge(v2,4);
v3.addEdge(v6,3);
v4.addEdge(v1,3);
v4.addEdge(v5,5);
v4.addEdge(v7,2);
v5.addEdge(v2,1);
v5.addEdge(v4,1);
v5.addEdge(v6,3);
v6.addEdge(v3,2);
v6.addEdge(v5,3);
v6.addEdge(v6,4);
v7.addEdge(v4,2);
v7.addEdge(v6,2);
List<Node> campus = Arrays.asList(v1, v2, v3, v4, v5, v6, v7);
// 从图书馆出发计算最短路径
// unweighted(v1);
// 从图书馆出发计算最短路径
dijkstra(v1);
// 打印结果
printResults(campus);
// 额外测试:从图书馆到校门的最短路径
System.out.println("\n从图书馆到校门的最短路径:");
List<Node> pathToGate = reconstructPath(v7);
for (Node v : pathToGate) {
System.out.print(v.name + (v != v7 ? " → " : ""));
}
}
}
- 运行结果
顶点 距离 前驱 路径
--------------------------------------
图书馆 0 null 图书馆
教学楼A 5 图书馆 图书馆 → 教学楼A
教学楼B 7 宿舍 图书馆 → 食堂 → 校门 → 宿舍 → 教学楼B
食堂 1 图书馆 图书馆 → 食堂
体育馆 6 食堂 图书馆 → 食堂 → 体育馆
宿舍 5 校门 图书馆 → 食堂 → 校门 → 宿舍
校门 3 食堂 图书馆 → 食堂 → 校门
从图书馆到校门的最短路径:
图书馆 → 食堂 → 校门