SPFA与迪杰斯特拉

124 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

SPFA(Shortest Path Faster Algorithm)是Bellman-Ford算法的一种队列实现,减少了不必要的冗余计算。

算法:用一个队列来进行维护。 初始时将源加入队列。 每次从队列中取出一个元素,并对所有与他相邻的点进行松弛,若某个相邻的点松弛成功,则将其入队。 直到队列为空时算法结束。

这个算法,简单的说就是队列优化的bellman-ford,利用了每个点不会更新次数太多的特点发明的此算法。SPFA——Shortest Path Faster Algorithm,它可以在O(kE)的时间复杂度内求出源点到其他所有点的最短路径,可以处理负边。SPFA的实现甚至比Dijkstra或者Bellman_Ford还要简单

SPFA可以处理负权边

定理: 只要最短路径存在,上述SPFA算法必定能求出最小值。

证明:

  每次将点放入队尾,都是经过松弛操作达到的。换言之,每次的优化将会有某个点v的最短路径估计值d[v]变小。所以算法的执行会使d越来越小。由于我们假定图中不存在负权回路,所以每个结点都有最短路径值。因此,算法不会无限执行下去,随着d值的逐渐变小,直到到达最短路径值时,算法结束,这时的最短路径估计值就是对应结点的最短路径值。

期望的时间复杂度O(ke), 其中k为所有顶点进队的平均次数,可以证明k一般小于等于2。

判断有无负环:如果某个点进入队列的次数超过N次则存在负环(SPFA无法处理带负环的图)

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<sstream>
#include<vector>
#include<map>
#include<queue>
#include<stack>
#include<set>
#include<cmath>
#define up(i, x, y) for(int i = x; i <= y; i++)
#define down(i, x, y) for(int i = x; i >= y; i--)
#define MAXN ((int)2e4 + 10)
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
struct Edge
{
    int v, cost;
    Edge(int v_ = 0, int cost_ = 0) : v(v_), cost(cost_){}
};
vector<Edge> E[MAXN];
bool vis[MAXN];
int dist[MAXN];
int cnt[MAXN];
void addedge(int u, int v, int w)
{
    E[u].push_back(Edge{v, w});
}
bool SPFA(int start, int n)
{
    memset(vis, false, sizeof(vis));
    for(int i = 1; i <= n; i++){
        dist[i] = INF;
    }
    vis[start] = true;
    dist[start] = 0;
    queue<int> que;
    while(!que.empty()){
        que.pop();
    }
    que.push(start);
    memset(cnt, 0, sizeof(cnt));
    cnt[start] = 1;
    while(!que.empty()){
        int u = que.front(); que.pop();
        vis[u] = false;
        for(int i = 0; i < E[u].size(); i++){
            int v = E[u][i].v;
            if(dist[v] > dist[u] + E[u][i].cost){
                dist[v] = dist[u] + E[u][i].cost;
                if(!vis[v]){
                    vis[v] = true;
                    que.push(v);
                    if(++cnt[v] > n) return false; //超过入队次数上限,说明有负环
                }
            }
        }
    }
    return true;
}

int main()
{
    int n, m;scanf("%d %d", &n, &m);
    up(i, 1, m)
    {
        int x, y, w; scanf("%d %d %d", &x, &y, &w);
        addedge(x, y, w);
    }
    if(SPFA(1, n)){
        up(i, 2, n) printf("%d\n", dist[i]);
    }
}

 迪杰斯特拉算法:

本质是贪心,最近的最可能最近,换句话说绕远路肯定不是最优的,所以导致没有办法求解带有负权的最短路径。因为带负权的最短路可能就是绕的远路得到的。例如:

利用迪杰斯特拉求出来的最短路为 0 4 0 , 实际为 0 2 0; 就是因为先访问的2号节点,之后没办法通过更远的3号节点去更新4号节点所以不行。这么看起来迪杰斯特拉有种BFS的感觉。

bool vis[100000 + 7];
    int  dis[100000 + 7];
    int  cnt[100000 + 7];
    vector<vector<P>> G;

    bool dijkstra(int s, int n)
    {
        memset(vis, false, sizeof(vis));
        for(int i = 0; i < n; i++) dis[i] = 0x3f3f3f3f;
        vis[s] = 1;
        dis[s] = 0;
        priority_queue<P, vector<P>, greater<P>> que;
        que.push(P{0, s});

        while(!que.empty())
        {
            P cur = que.top(); que.pop();
            vis[cur.second] = 1;
            int v = cur.second;
            if(cur.first > dis[v]) continue; // old
            for(int i = 0; i < G[v].size(); i++)
            {
                int nxt = G[v][i].first;
                int cost = G[v][i].second;

                if(vis[nxt] == 0 && dis[nxt] > dis[v] + cost )
                {
                    dis[nxt] = dis[v] + cost;
                    que.push(P{dis[nxt], nxt});
                }
            }
        }
        return 1;
    }

例题代码:

typedef pair<int, int> P;
class Solution {
public:
    bool vis[100000 + 7];
    int  dis[100000 + 7];
    int  cnt[100000 + 7];
    vector<vector<P>> G;

    bool dijkstra(int s, int n)
    {
        memset(vis, false, sizeof(vis));
        for(int i = 0; i < n; i++) dis[i] = 0x3f3f3f3f;
        vis[s] = 1;
        dis[s] = 0;
        priority_queue<P, vector<P>, greater<P>> que;
        que.push(P{0, s});

        while(!que.empty())
        {
            P cur = que.top(); que.pop();
            vis[cur.second] = 1;
            int v = cur.second;
            if(cur.first > dis[v]) continue; // old
            for(int i = 0; i < G[v].size(); i++)
            {
                int nxt = G[v][i].first;
                int cost = G[v][i].second;

                if(vis[nxt] == 0 && dis[nxt] > dis[v] + cost )
                {
                    dis[nxt] = dis[v] + cost;
                    que.push(P{dis[nxt], nxt});
                }
            }
        }
        return 1;
    }

    bool spfa(int s, int n)
    {
        memset(cnt, 0, sizeof(cnt));
        memset(vis, false, sizeof(vis));
        for(int i = 0; i < n; i++)
        {
            dis[i] = 0x3f3f3f3f;
        }
        vis[s] = 1;
        dis[s] = 0;
        queue<int> que;
        que.push(s);
        cnt[s] = 1;
        
        while(!que.empty())
        {
            int cur = que.front(); que.pop();
            vis[cur] = 0;
            
            for(int i = 0; i < G[cur].size(); i++)
            {
                int v = G[cur][i].first;
                // cout << "v = " << v << "\n";
                if(dis[v] > dis[cur] + G[cur][i].second)
                {
                    dis[v] = dis[cur] + G[cur][i].second;
                    if(!vis[v])
                    {
                        vis[v] = 1;
                        que.push(v);
                        if(++cnt[v] > n+1) return 0;
                    }
                }
            }
        }
        return 1;
    }
    
    int minimumObstacles(vector<vector<int>>& grid) {
        
        int num = grid.size() * grid[0].size();
        
        G.resize(num);
        
        for(int i = 0; i < grid.size(); i++)
        {
            for(int j = 0; j < grid[i].size(); j++)
            {
                int curid = i * grid[i].size() + j;
                
                if(j < grid[i].size() - 1)
                {
                    int nxtid = curid + 1;
                    G[curid].push_back(P{nxtid, grid[i][j + 1]});
                    G[nxtid].push_back(P{curid, grid[i][j]});
                }
                if(i < grid.size() - 1)
                {
                    int nxtid = curid + grid[i].size();
                    G[curid].push_back(P{nxtid, grid[i + 1][j]});
                    G[nxtid].push_back(P{curid, grid[i][j]});

                }
            }
        }
        
        // spfa(0, num);
        dijkstra(0, num);

        return dis[num - 1];
    }
};