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]为源点sv 的最短路径长度,但该路径仅经过集合S 中的顶点。即路径{s(viS)v} 的最小长度
  • 若路径是按照递增(非递减)的顺序生成的,则
    • 真正的最短路必须只经过集合S 中的顶点
    • 每次从未收录的顶点中选一个dist最小的收录(贪心)
    • 增加一个v 进入集合S ,可能影响另外一个邻接点w 的dist值
      • 如果收录v 使得源点sw 的路径变短,那么sw 的路径一定经过v ,且vw 有一条边
      • 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] 即给出了ij 的真正最短距离
  • 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数组进行递归求解即可

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章