最短路径问题 Shortest Path

广度优先遍历

最短路径树 Shorted Path Tree

单源最短路径 Single Shorted Path Tree。

无权图的最短路径和带权图的最短路径不同,带权图需要考虑松弛操作(Relaxation)。

松弛操作是最短路径求解的核心。

Dijkstra 单源最短路径算法

前提:不能有负权边。

生活中大部分的图,是不存在负权边的。

复杂度:$O(Elog(V))$

借助最小索引堆。

初始,对起点进行标识,对它所有的邻边进行访问。

接下来就 Dijkstra 算法非常重要的一步。此时找到没有访问的顶点中,能够以最短的方式抵达的那个顶点,此时源点到达此顶点的最短路径确定。如下图 0 —> 2 的最短路径就是 2。

因为 0 到其他顶点的花费都高于 2,此时再折回来时,花费只会更大。(图中没有负权边)

确定新的节点的最短路径后,进行 Relaxation 操作。遍历新节点 2 的所有邻边,经过中转站 2 到达 1 比 0 直接到达 1 花费更少,此时更新花费。

此时从未访问的所有顶点中找出花费最小的顶点,就是源点到此节点的最短花费。

对 1 节点进行松弛操作。以 1 为中转站更新。

此时就 3、4 节点未访问,取最小花费的节点 4,确定最短路径。

对 4 所有的邻边进行松弛操作。从 4 无法到达任何节点,此时不需要进行任何松弛操作。

最后只有 3 这个节点没有被访问过,这就找到了从源点到 3 的最短路径。

实现细节

IndexMinHeap 获取最小边的时间复杂度为 $log(V)$。

对所有的边进行操作,最后的时间复杂度就是 $Elog(V)$ 。

#include <iostream>
#include <queue>
#include <cstdio>
using namespace std;

const int n = 12;

#define inf INT32_MAX

int min_distance(int dist[], bool visited[])  { 
    // Initialize min value 
  int min = INT32_MAX, min_index; 
  for (int v = 1; v < n; v++) 
    if (visited[v] == false && dist[v] <= min) {
        min = dist[v];
        min_index = v;
    }
  return min_index; 
}

// A utility function to print the constructed distance array 
void print_solution(int dist[], int s) { 
    printf("s -> d :  Distance from Source to destination\n"); 
    for (int i = 1; i < n; i++) {
      if (dist[i] == inf)
        printf("%d -> %d: inf\n", s, i);
      else
        printf("%d -> %d: %d\n", s, i, dist[i]);
    }
}

void dijkstra(int g[n][n], int s) {
  bool visited[n] = {false}; // 初始化所有顶点未访问

  int dist[n];       // dist[i] is shortest distance from s to i
  for (int i  = 0; i < n; ++i)
    dist[i] = INT32_MAX;
  dist[s] = 0;       // Distance of source vertex from itself is always 0 

  for (int i = 0; i < n; ++i) {
    int u = min_distance(dist, visited);
    visited[u] = true;

    for (int v = 1; v < n; ++v) {
      if (!visited[v] && g[u][v] && g[u][v] != inf && (dist[u] != INT32_MAX) && dist[u] + g[u][v] < dist[v])
        dist[v] = dist[u] + g[u][v];
    }
  }
  print_solution(dist, s); 
}

int main() {
  // int c[n][n] = { {0,0,0,0,0,0}, 
  //                 {0,0,2,3,5000,5000},
  //                 {0,5000,0,1,2,5000},
  //                 {0,5000,5000,0,9,2},
  //                 {0,5000,5000,5000,0,2},
  //                 {0,5000,5000,5000,5000,0}};

  // int c[n][n] = { {0,0,0,0,0},
  //               {0,0,2,3,5000},
  //               {0,5000,0,1,2},
  //               {0,5000,5000,0,9},
  //               {0,5000,5000,5000,0}};
  
  int c[n][n] = { {0,0,0,0,0,0,0,0,0,0,0,0},
                  {0,0,2,3,4,inf,inf,inf,inf,inf,inf,inf},
                  {0,inf,0,3,inf,7,2,inf,inf,inf,inf,inf},
                  {0,inf,inf,0,inf,inf,9,2,inf,inf,inf,inf},
                  {0,inf,inf,inf,0,inf,inf,2,inf,inf,inf,inf},
                  {0,inf,inf,inf,inf,0,inf,inf,3,3,inf,inf},
                  {0,inf,inf,inf,inf,inf,0,1,inf,3,inf,inf},
                  {0,inf,inf,inf,inf,inf,inf,0,inf,5,1,inf},
                  {0,inf,inf,inf,inf,inf,inf,inf,0,inf,inf,3},
                  {0,inf,inf,inf,inf,inf,inf,inf,inf,0,inf,2},
                  {0,inf,inf,inf,inf,inf,inf,inf,inf,2,inf,2},
                  {0,inf,inf,inf,inf,inf,inf,inf,inf,inf,inf,0}}; //邻接矩阵
  dijkstra(c, 1);
 
  return 0;
}

参考资料