13. 图--最短路径问题

最短路径问题

问题抽象

在网络中,求两个不同顶点之间的所有路径中,边的权值之和最小的那一条路径

  • 这条路径就是两点之间的最短路径(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(viS)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{lk}j} 的最小长度
  • D0,D1,...,DV1[i][j] 即给出了 i j 的真正最短距离
  • D1 被初始化为带权邻接矩阵,对角元为0,顶点之间没有直接的边相连则值初始化为正无穷大
  • Dk1 已经完成,递推到 Dk 时:
    • 如果 k 最短路径 {i{lk}j} ,则 Dk=Dk1
    • 如果 k 最短路径 {i{lk}j} ,则该路径必定由两段最短路径组成 Dk[i][j]=Dk1[i][k]+Dk1[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数组进行递归求解即可

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值