最短路径问题
问题抽象
在网络中,求两个不同顶点之间的所有路径中,边的权值之和最小的那一条路径
- 这条路径就是两点之间的最短路径(Shortest Path)
- 第一个顶点为源点(Source)
- 最后一个顶点为终点(Destination)
问题分类
- 单源最短路径问题:从某固定源出发,求其到所有其他顶点的最短路径
- (有向)无权图
- (有向)有权图
- 多源最短路径问题:求任意两顶点间的最短路径
单源最短路算法
无权图
按照路径长度递增(非递减)的顺序找出到各个顶点的最短路(遍历方式跟BFS类似)
实现
- dist[W] : 源点
S 到顶点W 的最短距离
- dist[S]:源点
S 到源点S 的距离初始化为0 - 其他的dist[W]可以被初始化为正无穷、负无穷以及-1(算法中采用初始化为-1)
- dist[S]:源点
- 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]这里被初始化为正无穷
- dist[S]:源点
- 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)
- 将dist存在最小堆中:
多源最短路算法
若有N个顶点、E条边:
* 方法1:直接将单源最短路算法调用
* 方法2:Floyd算法,时间复杂度为
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数组进行递归求解即可