最短路径问题
问题抽象
在网络中,求两个不同顶点之间的所有路径中,边的权值之和最小的那一条路径
- 这条路径就是两点之间的最短路径(Shortest Path)
- 第一个顶点为源点(Source)
- 最后一个顶点为终点(Destination)
问题分类
- 单源最短路径问题:从某固定源出发,求其到所有其他顶点的最短路径
- (有向)无权图
- (有向)有权图
- 多源最短路径问题:求任意两顶点间的最短路径
单源最短路算法
无权图
按照路径长度递增(非递减)的顺序找出到各个顶点的最短路(遍历方式跟BFS类似)
实现
- dist[W] : 源点
S
到顶点
W
的最短距离
- dist[S]:源点 S 到源点 S 的距离初始化为0
- 其他的dist[W]可以被初始化为正无穷、负无穷以及-1(算法中采用初始化为-1)
- path[W]:源点
S
到顶点
W
的路上经过的某顶点
- 获取源点 S 到顶点 W 的路径:从 W 开始去反向遍历到 S 即可获取到反向的路径,再由栈进行正向的处理即可
void Unweighted(Vertex S) {
Enqueue(S, Q);
while (!IsEmpty(Q)) {
V = Dequeue(Q);
for (V 的每个邻接点 W) {
if (dist[W] == -1) { // 说明还没有访问过
dist[W] = dist[V] + 1; // 为上一个顶点的距离+1
path[W] = V; // 记录为上一个顶点
Enqueue(W, Q);
}
}
}
}
若有N个顶点、E条边,时间复杂度是
- 用邻接表存储图,为 O(N+E)
- 用邻接矩阵存储图,为 O(N2)
有权图
按照路径长度递增(非递减)的顺序找出到各个顶点的最短
Dijkstra 算法用于解决有权图的单源最短路问题。但是不能解决有负边的情况
Dijkstra 算法
- 令 S={ 源点 s + 已经确定了最短路径的顶点 vi}
- 对任一为收录的顶点 v ,定义dist[v]为源点 s 到 v 的最短路径长度,但该路径仅经过集合 S 中的顶点。即路径 {s→(vi∈S)→v} 的最小长度
- 若路径是按照递增(非递减)的顺序生成的,则
- 真正的最短路必须只经过集合 S 中的顶点
- 每次从未收录的顶点中选一个dist最小的收录(贪心)
- 增加一个
v
进入集合
S
,可能影响另外一个邻接点
w
的dist值
- 如果收录 v 使得源点 s 到 w 的路径变短,那么 s 到 w 的路径一定经过 v ,且 v 到 w 有一条边
- dist[w]=min{dist[w],dist[v]+<v,w> 的权重 }
实现
- collected[v]:记录顶点 v 是否被收录
- dist[W] : 源点
s
到顶点
w
的最短距离
- dist[S]:源点 s 到源点 s 的距离初始化为0
- 其他的dist[W]这里被初始化为正无穷
- path[W]:源点
s
到顶点
w
的路上经过的某顶点
- 获取源点 s 到顶点 w 的路径:从 w 开始去反向遍历到 s 即可获取到反向的路径,再由栈进行正向的处理即可
void Dijkstra(Vertex s) {
while (true) {
V = 未收录顶点中dist最小者;
if ( 这样的V不存在 )
break;
collected[V] = true;
for ( V 的每个邻接点 W) {
if(!collected[W] && dist[V] + E<V, W> < dist[W]) {
dist[W] = dist[V] + E<V, W>;
path[W] = V;
}
}
}
}
若有N个顶点、E条边,根据寻找未收录顶点中dist最小者的方式来决定,时间复杂度是
- 方法1,更适用与稠密图
- 直接扫描所有未收录顶点: O(N)
- 最终的时间复杂度: T=O(N2+E)
- 方法2,更适用于稀疏图
- 将dist存在最小堆中: O(logN)
- 更新dist[w]的值: O(logN)
- 最终的时间复杂度: T=O(NlogN+ElogN)=O(ElogN)
多源最短路算法
若有N个顶点、E条边:
* 方法1:直接将单源最短路算法调用
N
遍,时间复杂度为
T=O(N3+NE)
,适合稀疏图
* 方法2:Floyd算法,时间复杂度为
O(N3)
,适合稠密图
Floyd算法
- Dk[i][j]= 路径 {i→{l≤k}→j} 的最小长度
- D0,D1,...,DV−1[i][j] 即给出了 i 到 j 的真正最短距离
- D−1 被初始化为带权邻接矩阵,对角元为0,顶点之间没有直接的边相连则值初始化为正无穷大
- 当
Dk−1
已经完成,递推到
Dk
时:
- 如果 k∉ 最短路径 {i→{l≤k}→j} ,则 Dk=Dk−1
- 如果 k∈ 最短路径 {i→{l≤k}→j} ,则该路径必定由两段最短路径组成: Dk[i][j]=Dk−1[i][k]+Dk−1[k][j]
实现
void Floyd() {
int i, j, k;
for (i = 0; i < N; i++)
for (j = 0; j < N; j++) {
D[i][j] = G[i][j];
path[i][j] = -1;
}
for (k = 0; k < N; k++)
for (i = 0; i < N; i++)
for (j = 0; j < N; j++)
if (D[i][k] + D[k][j] < D[i][j]) {
D[i][j] = D[i][k] + D[k][j];
path[i][j] = k;
}
}
如果需要求出最短路径经过哪些点,通过path数组进行递归求解即可