适用范围: 单源点到其他节点的最短路径求解;不包括负权边(因为是基于贪心的,边相加会变大才行);
1. 传统最短路算法
将所有节点分成两类:已确定从起点到当前点的最短路径长度的节点,以及未确定从起点到当前点的最短路径长度的节点(下面简称[未确定节点] 和 [以确定节点])。
每次从「未确定节点」中取一个与起点距离最短的点,将它归类为「已确定节点」,并用它「更新」从起点到其他所有「未确定节点」的距离。直到所有点都被归类为「已确定节点」。
用节点A
「更新」节点B
的意思是,用起点到节点A
的最短路长度加上从节点A
到节点B
的边的长度,去比较起点到节点B
的最短路长度,如果前者小于后者,就用前者更新后者。这种操作也被叫做「松弛」。
时间复杂度:O(n^2)
空间复杂度:O(m)
,其中 m
为图中边的数量。
java代码实现
// 邻接矩阵
int[][] mapp = new int[n][n];
// 最短路记录
int[] dis = new int[n];
// visited[i] = true 表示 i 节点已找到最短路径
boolean[] visited = new int[n];
// 找 v0 到其他节点的最短路
public void Dijkstra(int v0) {
// 初始化
for (int i = 1; i <= n; i++) {
dis[i] = mapp[v0][i];
visited[i] = false;
}
// 初始 v0 已找到最短路
visited[v0] = true;
// 每次执行找到一个节点的最短路径
for (int i = 1; i <= n; i++) {
int p, minn = Integer.MAX_VALUE;
// 找到当前的最短路径
for (int j = 1; j <= n; j++) {
if (!visited[j] && dis[j] < minn) {
p = j;
minn = dis[j];
}
}
// 记录
visited[p] = true;
// 松弛操作,更新与其他点的距离
for (int j = 1; j <= n; j++) {
if (!visited[j] && dis[p] + mapp[p][j] < dis[j]) {
dis[j] = dis[p] + mapp[p][j];
}
}
}
return;
}
2. 基于堆优化的Dijkstra 算法
**适用范围:**稀疏图,因为堆的时间复杂度与边的数量有关。
概念:
传统方法中每次需要在[未确定节点]中找到距离最小的节点,这个时间复杂度时
O(N)
, 在这个步骤上利用堆的性质进行优化,用小顶推进行存储[未确定节点]。堆的存取时间为O(logN)
,因此可以节省时间。
Java代码实现
首先对节点进行封装:
// 自定义比较类
class Pair implements Comparable<Pair>{
int node;
int distance;
public Pair(int node, int distance){
this.node = node;
this.distance = distance;
}
@Override
public int compareTo(Pair pair2) {
return this.distance - pair2.distance;
}
}
Pair 类代表的意思是某个节点到 node 节点的距离是 distance 。重写了compareTo
方法使之可以入队后默认从小到大排序。也可以用长度为2的一维数组代替。
整体代码:
/**
n: 节点个数
edges: egdes[i] = [1, 2, 3] 代表1到2有路径,距离为3。
本方法求第 n 个节点到其他节点的最短路径
*/
public int[] Dijkstra(int n, int[][] edges) {
int INF = Integer.MAX_VALUE;
// 构建邻接表 graph.get(i) 也是个list,存储与节点 i 直接相邻的节点以及距离(Pari 对象)
List<List<Pair>> graph = new ArrayList<>();
for (int i = 0; i <= n; i++) {
graph.add(new ArrayList<Pair>());
}
for (int[] edge : edges){
graph.get(edge[0]).add(new Pair(edge[1], edge[2]));
graph.get(edge[1]).add(new Pair(edge[0], edge[2]));
}
// 优先级队列
PriorityQueue<Pair> pq = new PriorityQueue<Pair>();
// 要求 n 到其他节点的最短路径,因此将 n 的邻接节点封装的 Pair 实体入队。
for(Pair pair : graph.get(n)){
pq.offer(pair);
}
// 答案数组,默认值为 INF
int[] res = new int[n];
Arrays.fill(res, INF);
// 到自己的最短路径是 0
res[n - 1] = 0;
while (!pq.isEmpty()){
// 队头元素就是当前距离最短的节点元素,无需遍历
Pair poll = pq.poll();
int node = poll.node;
int dis = poll.distance;
// 如果比答案里的要大,说明在这之前就已经通过其他节点找到了更优的路径,不做操作。
if(dis > res[node - 1]){
continue;
}
// 设置最短路径
res[node - 1] = dis;
// 通过该节点对其他节点进行松弛操作
for(Pair pair : graph.get(node)){
// 松弛成功,更新最短路径,并将新的路径信息封装成 Pair 对象入队。
if(res[pair.node - 1] > dis + pair.distance){
res[pair.node - 1] = dis + pair.distance;
pq.offer(new Pair(pair.node, res[pair.node - 1]));
}
}
}
return res;
}