最短路径-SPFA算法

组别:提高组
难度:6

SPFA(Shortest Path Faster Algorithm)算法是用来解决单源最短路径问题的一种算法,属于Bellman-Ford算法的改进版本。SPFA算法主要用于求解图中一个顶点到其他顶点的最短路径,特别适用于有负权边的图。

SPFA算法的主要思想:

SPFA算法和Bellman-Ford算法类似,都是基于松弛操作(relaxation operation)。但SPFA通过使用队列来优化Bellman-Ford算法中的松弛过程,避免了不必要的松弛操作,从而提高了效率。

SPFA算法的步骤:

  1. 初始化
    • 将源点的距离设为0,其他所有顶点的距离设为∞(或一个非常大的数)。
    • 将源点加入队列。
  2. 松弛操作
    • 从队列中取出一个顶点 u,对于从 u 出发的每一条边 (u, v)
      • 如果 dist[v] > dist[u] + weight(u, v),则更新 dist[v],并将顶点 v 加入队列(如果 v 不在队列中)。
    • 重复该过程,直到队列为空。
  3. 检测负环
    • 如果在执行过程中,某个顶点被加入队列的次数超过顶点数目,则说明图中存在负环。

SPFA的优势:

相比Bellman-Ford算法,SPFA算法在一般情况下更快,特别是在稀疏图中效果显著。不过,最坏情况下的时间复杂度仍然为O(VE),其中V是顶点数,E是边数。

代码实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/**************************************************************** 
 * Description: SFPA算法
 * Author: Alex Li
 * Date: 2024-08-13 10:30:24
 * LastEditTime: 2024-08-13 10:30:32
****************************************************************/
#include <iostream>
#include <vector>
#include <queue>
#include <climits>

using namespace std;

// 定义一个常量表示无穷大(不可达的距离)
const int INF = INT_MAX;

// 定义边的结构体,包含目标顶点和边的权重
struct Edge {
    int v, weight;
};

// SPFA算法函数,计算从源点source到其他顶点的最短路径
void SPFA(int source, vector<vector<Edge>>& graph, vector<int>& dist) {
    int n = graph.size();  // 获取图的顶点数
    vector<bool> inQueue(n, false);  // 标记每个顶点是否在队列中
    vector<int> count(n, 0);  // 记录每个顶点进入队列的次数,用于检测负权环
    queue<int> q;  // 用于SPFA算法的队列

    // 初始化源点的距离为0,并将源点加入队列
    dist[source] = 0;
    q.push(source);
    inQueue[source] = true;

    // 当队列不为空时,执行以下操作
    while (!q.empty()) {
        int u = q.front();  // 取出队首顶点
        q.pop();  // 从队列中移除该顶点
        inQueue[u] = false;  // 标记该顶点不在队列中

        // 遍历从顶点u出发的所有边
        for (auto& edge : graph[u]) {
            int v = edge.v;  // 获取边的目标顶点
            int weight = edge.weight;  // 获取边的权重

            // 松弛操作:如果从u到v的距离更短,则更新dist[v]
            if (dist[u] != INF && dist[v] > dist[u] + weight) {
                dist[v] = dist[u] + weight;  // 更新目标顶点v的最短距离
                // 如果目标顶点v不在队列中,则将其加入队列
                if (!inQueue[v]) {
                    q.push(v);
                    inQueue[v] = true;  // 标记目标顶点v已加入队列
                    count[v]++;  // 增加目标顶点v进入队列的次数

                    // 如果某个顶点进入队列的次数超过顶点数,则说明存在负权环
                    if (count[v] > n) {
                        cout << "Negative weight cycle detected!" << endl;
                        return;  // 退出算法,检测到负权环
                    }
                }
            }
        }
    }
}

int main() {
    int n, m;  // n是顶点数,m是边数
    cin >> n >> m;

    // 使用邻接表表示图,graph[i]是一个包含从顶点i出发的所有边的列表
    vector<vector<Edge>> graph(n);

    // 输入所有边的信息
    for (int i = 0; i < m; ++i) {
        int u, v, w;
        cin >> u >> v >> w;
        graph[u].push_back({v, w});  // 添加从顶点u到顶点v,权重为w的边
    }

    int source;  // 输入源点
    cin >> source;

    // 初始化最短距离数组,所有顶点的初始距离为INF(无穷大)
    vector<int> dist(n, INF);

    // 调用SPFA算法计算最短路径
    SPFA(source, graph, dist);

    // 输出结果,即从源点到各顶点的最短距离
    for (int i = 0; i < n; ++i) {
        if (dist[i] == INF) {
            cout << "INF ";  // 如果顶点不可达,输出INF
        } else {
            cout << dist[i] << " ";  // 输出从源点到顶点i的最短距离
        }
    }
    cout << endl;

    return 0;  // 程序结束
}

详细注释说明:

  • Edge结构体:定义了图中的一条边,包含目标顶点和权重。
  • SPFA函数:实现了SPFA算法,用于计算从指定源点出发到图中所有其他顶点的最短路径。使用了队列和标记数组来管理顶点的松弛操作。
  • 初始化部分:设置源点距离为0,将其加入队列,并标记为已在队列中。
  • 松弛操作:检查从当前顶点出发的每条边,如果可以通过当前顶点得到更短的路径,则更新目标顶点的距离。如果目标顶点不在队列中,则将其加入队列。
  • 负权环检测:如果某个顶点进入队列的次数超过顶点总数,说明存在负权环,程序终止并输出警告。
Scroll to Top