本文已参与「新人创作礼」活动,一起开启掘金创作之路。
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];
}
};