SPFA最短路算法(稀疏图的更优选择)(允许负权)(邻接表)(简单)

本文详细介绍了求解图中1号点到所有点最短路径的SPFA(Shortest Path Faster Algorithm)算法。该算法适用于稀疏图,平均时间复杂度为Om,最坏情况为On*m。通过队列模拟,不断更新节点的最短距离,并避免负权环的影响。在代码实现中,使用数组记录距离并维护一个队列,直到无法更新最短距离为止。同时,利用path[]数组记录最短路径。SPFA算法是Dijkstra算法的一种优化,尤其适用于存在负权重的情况。
  1. 问题定义:求1号点到所有点的最短路径

  2. 时间复杂度:平均Om 最差On*m 适合稀疏图(邻接表建图方式 前面文章有提到)

  3. 算法思想

    1. 把1号点加入队列(数组模拟队列,前面有提)。
    2. 不断的取点,不断的循环所有边,不断的更新最短距离(故禁止负环出现)。 很像堆dij
    3. 退出条件是等所有的最短距离都无法更新的时候,队列为空。(队列只是个更新容器)
  4. 队列里面方的是:距离1号点距离变小的点

  5. 代码:

      static int spfa(){
          Arrays.fill(dist,0x3f3f3f3f);
          dist[1] = 0;
          push(1);
          st[1] = true;  //标记点已经在队列中。
          while(hh<=tt){
              int t = pop();
              st[t] = false;  //出队 可重新入队。
              for(int i = h[t];i!=-1;i = NE[i]){ //选择所有的临1边
                  int j = E[i];   //这里是t通向的所有边。
                  if(dist[j]>dist[t]+w[i]){
                      dist[j] = dist[t]+w[i]; //有更小的不管有没有在队列里,在这里都要被更新。队列只是一个用来访问变动过的点的容器罢了。那个点变动过,那么就要更新那个点后面的距离而已。(允许负权的地方)
                      if(st[j] == false) {  //只是判断有没有在队列中。
                          push(j);  //只存距离变小了的点。
                          st[j] = true;
                      }
                  }
              }
          }
         // if(dist[n]==0x3f3f3f3f) return -1; //不能-1 直接返回
          //在外面判断是否为 INF 即可, -1 也可能是最短路
       return dist[n];
      }
      ```
    
    
  6. 输出最短路

    我们定义一个path[]数组,path[i]表示源点s到i的最短路程中,结点i之前的结点的编号(父结点),我们在借助结点u对结点v更新的同时,标记下path[v]=u,记录的工作就完成了(记录的永远是最后一次更新)。 然后递归输出

### 边的定义 在图论中,边可以带有重,这些重可以表示距离、成本、时间等。当某条边的重为数时,这条边被称为**边**。边的存在会对短路径的计算带来复杂性,因为某些算法(如 Dijkstra)无法处理边,尤其是当图中存在**环**时[^1]。 边的一个典型例子是:假设你在一个城市间旅行,某些路线不仅不消耗费用,反而会给你补贴,这种情况下就可以用边来表示。 --- ### SPF(Shortest Path Faster Algorithm)算法处理边的原理 SPFA算法是Bellman-Ford算法的一种优化版本,专门用于处理含有边的图结构,同时它也可以检测图中是否存在**环**[^2]。 #### SPFA算法的核心思想: 1. **松弛操作**: - 与Bellman-Ford算法类似,SPFA通过不断松弛边来更新节点的短路径估计值。松弛操作的逻辑是:如果从起点到节点`u`的短路径已知,并且从`u`到`v`的边值为`w`,那么只有当`dist[u] + w < dist[v]`时,才会更新`dist[v]`的值。 2. **队列优化**: - SPFA使用一个队列来记录需要进行松弛操作的节点。每当某个节点的短路径被更新时,该节点会被加入队列中,以便后续对其相邻边进行松弛。 - 这种优化避免了Bellman-Ford算法中对所有边重复处理的冗余操作,从而提高了算法效率。 3. **环检测**: - 如果某个节点被加入队列的次数超过图中节点的总数(即`n`次),则说明图中存在**环**。环会导致短路径无限小,因此在这种情况下无法求解短路径。 #### SPFA的时间复杂度 - **平均时间复杂度**:`O(m)`,其中`m`是图中的边数。 - **坏时间复杂度**:`O(n * m)`,其中`n`是图中的节点数。 - 在实际应用中,SPFA的性能通常优于传统的Bellman-Ford算法,尤其是在稀疏图上。 --- ### SPFA算法的实现示例 以下是一个基于邻接表SPFA算法实现,用于计算单源短路径: ```cpp #include <iostream> #include <vector> #include <queue> #include <cstring> using namespace std; const int MAXN = 10005; const int INF = 0x3fffffff; vector<pair<int, int>> adj[MAXN]; // 邻接表存储图结构,pair<目标节点, 边> int dist[MAXN]; // 存储短路径 bool inQueue[MAXN]; // 标记节点是否在队列中 int cnt[MAXN]; // 记录每个节点入队列的次数 bool spfa(int n, int start) { queue<int> q; memset(dist, INF, sizeof(dist)); memset(inQueue, false, sizeof(inQueue)); memset(cnt, 0, sizeof(cnt)); dist[start] = 0; q.push(start); inQueue[start] = true; cnt[start]++; while (!q.empty()) { int u = q.front(); q.pop(); inQueue[u] = false; for (auto edge : adj[u]) { int v = edge.first; int w = edge.second; if (dist[u] + w < dist[v]) { dist[v] = dist[u] + w; if (!inQueue[v]) { q.push(v); inQueue[v] = true; cnt[v]++; if (cnt[v] >= n) { return false; // 检测到环 } } } } } return true; // 没有环 } int main() { int n, m; cin >> n >> m; for (int i = 0; i < m; i++) { int u, v, w; cin >> u >> v >> w; adj[u].push_back({v, w}); } if (spfa(n, 1)) { for (int i = 1; i <= n; i++) { cout << "Node " << i << ": " << dist[i] << endl; } } else { cout << "Negative cycle detected!" << endl; } return 0; } ``` --- ### 适用场景 - **SPFA适用于**: - 图中存在边的情况。 - 需要判断图中是否存在环。 - 稀疏图邻接表存储)场景下性能较好。 - **不适用场景**: - 图中存在环,且需要计算短路径。 - 稠密图场景下,Floyd算法可能更优。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值