20230330 7.1. 最短路径问题

最短路径问题的抽象

  • 在网络中,求两个不同顶点之间的所有路径中,边的权值之和最小的那一条路径
    • 这条路径就是两点之间的最短路径(Shortest Path)
    • 第一个顶点为源点(Source)
    • 最后一个顶点为终点(Destination)

问题分类:

  • 单源最短路径问题:从某固定源点出发,求其到所有其他顶点的最短路径
    • (有向)无权图
    • (有向)有权图
  • 多源最短路径问题:求任意两顶点间的最短路径

无权图的单源最短路算法

按照递增(非递减)的顺序找出到各个顶点的最短路

使用BFS广度优先遍历

代码实现

/**
    * 无权图的单源最短路算法
    */
public void unweighted(int start, int[] dist, int[] path) {
    Queue<Integer> queue = new LinkedList<>();

    Arrays.fill(dist, -1);
    dist[start] = 0;
    Arrays.fill(path, -1);
    path[start] = start;

    queue.offer(start);

    while (!queue.isEmpty()) {
        int v = queue.poll();
        for (int w = 0; w < size; w++) {
            if (edges[v][w] == 1 && dist[w] == -1) {
                dist[w] = dist[v] + 1;
                queue.offer(w);
                path[w] = v;
            }
        }
    }
}

有权图的单源最短路算法

Dijkstra 算法(迪杰斯特拉算法)

  • 令S=
  • 对任一未收录的顶点v,定义dist[v]为s到v的最短路径长度,但该路径仅经过S中的顶点。即路径{s -> (vi ∈ S) -> v}的最小长度
  • 若路径是按照递增(非递减)的顺序生成的,则
    • 真正的最短路必须只经过S中的顶点(为什么?)
    • 每次从未收录的顶点中选一个dist最小的收录(贪心)
    • 增加一个v进入S,可能影响另外一个w的dist值!
      • dist[w] = min
  • 参考博客
void Dijkstra( Vertex s )
{ 
    while (1) {
        V = 未收录顶点中dist最小者;
        if ( 这样的V不存在 )
            break; 
        collected[V] = true;
        
        for ( V 的每个邻接点 W )
        if ( collected[W] == false ) 
            if ( dist[V]+E<V,W> < dist[W] ) {
                dist[W] = dist[V] + E<V,W> ;
                path[W] = V;
            }
    }
} 
/* 不能解决有负边的情况 */
  • 方法1:直接扫描所有未收录顶点 – O( |V| )
    • \(T = O( |V|^2 + |E| )\)
    • 对于稠密图效果好
  • 方法2:将dist存在最小堆中 – O( log|V| )
    • 更新dist[w]的值 – O( log|V| )
    • T = O( |V| log|V| + |E| log|V| ) = O( |E| log|V| )
    • 对于稀疏图效果好

代码实现

/**
    * 有权图的单源最短路算法
    */
public void dijkstra(int start, int[] dist, int[] prev) {
    boolean[] collected = new boolean[size];
    collected[start] = true;

    for (int i = 0; i < size; i++) {
        dist[i] = edges[start][i];
    }
    dist[start] = 0;
    Arrays.fill(prev, start);

    while (true) {
        // 寻找最短路径相接的节点
        int min = Integer.MAX_VALUE, v = -1;
        for (int j = 0; j < size; j++) {
            if (!collected[j] && min > dist[j]) {
                min = dist[j];
                v = j;
            }
        }
        if (v == -1) {
            // 无法找到
            break;
        }
        collected[v] = true;


        // 修正当前最短路径和前驱顶点
        // 可能存在从1-2-3比直接从1-3更短
        for (int k = 0; k < size; k++) {
            if (!collected[k]) {
                int tmp = (edges[v][k] == Integer.MAX_VALUE ? Integer.MAX_VALUE : (min + edges[v][k]));

                if (tmp < dist[k]) {
                    dist[k] = tmp;
                    prev[k] = v;
                }
            }
        }
    }
}

多源最短路算法

  • 方法1:直接将单源最短路算法调用|V|遍
    • \(T = O( |V|^3 + |E|*|V|)\)
    • 对于稀疏图效果好
  • 方法2:Floyd 算法
    • \(T = O( |V|^3 )\)
    • 对于稠密图效果好

Floyd 算法(弗洛伊德算法)

  • \(D^k[i][j] = 路径{ i \rightarrow \{ l \le k \} \rightarrow j }\) 的最小长度
  • \(D^0, D^1, …, D^{|V|-1}[i][j]\) 即给出了 i 到 j 的真正最短距离
  • 最初的 \(D^{-1}\) 是什么?
  • \(D^{k-1}\) 已经完成,递推到 \(D^k\) 时:
    • 或者k ∉ 最短路径 \({ i \rightarrow \{ l \le k \} \rightarrow j }\) ,则 \(D^k = D^{k-1}\)
    • 或者k ∈ 最短路径 \({ i \rightarrow { l \le k } \rightarrow j }\) ,则该路径必定由两段最短路径组成: \(D^k[i][j]=D^{k-1}[i][k]+D^{k-1}[k][j]\)
  • 参考博客
void Floyd() 
{
    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;
                }
}

代码实现

/**
    * 多源最短路算法
    */
public void floyd(int[][] dist, int[][] path) {
    for (int i = 0; i < size; i++) {
        for (int j = 0; j < size; j++) {
            dist[i][j] = edges[i][j];
            path[i][j] = j;
        }
    }

    // 计算最短路径
    for (int k = 0; k < size; k++) {
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                // 如果经过下标为k顶点路径比原两点间路径更短,则更新dist[i][j]和path[i][j]
                // 也就是i-k-j的距离小于i-j
                int tmp = (dist[i][k] == Integer.MAX_VALUE || dist[k][j] == Integer.MAX_VALUE) ? Integer.MAX_VALUE :
                        (dist[i][k] + dist[k][j]);
                if (dist[i][j] > tmp) {
                    dist[i][j] = tmp;
                    path[i][j] = path[i][k];
                }
            }
        }
    }
}
posted @ 2023-06-21 16:23  流星<。)#)))≦  阅读(13)  评论(0编辑  收藏  举报