SPFA最短路算法
SPFA:Shortest Path Faster Algoriithm 快速最短路径算法
SPFA的核心算法就是Bell-Ford算法。它使用一个队列或者一个栈来减少了Bell-Ford中不必要的松弛。可以处理负边和环的情况,他的使用比Dijstra更广泛。但是未经优化的SPFA算法时间复杂度不稳定,测试用例对它的影响较大。所以有的时候为了简单方便,我们直接使用Dijstra算法来解决。
SPFA实现的过程非常类似于BFS。他们都是使用队列来维护,不同的是,BFS的每个顶点只入队一次,而SPFA的结点则课能会入队多次。所以我们需要一个数组bool vis[] 来记录该顶点是否在队列中。SPFA实现过程如下:
步骤一: 首先将将源点s入队,将所有点到s的距离设置为dis[i]=∞,dis[s]=0
步骤二:如果队列为空,算法终止。否则取出队首元素u,将与u相连的边<u,v>进行松弛,如果dis[u]距离更新了且u不在队列中,则将u入队,若u进队次数大于n(总顶点数),则说明有负环,立即结束算法。
步骤三:不断地重复步骤二,直到算法结束
SPFA算法的时间复杂度取决于顶点总共的入队次数,他的算法时间复杂度为O(ke),e是边数,k是每个节点平均入队的次数,一般有k<2.
#include<queue> #define INF INT_MAX/2 #define MAX_SIZE 1000 int dis[MAX_SIZE]; //保存最短距离 bool vis[MAX_SIZE]; int a[MAX_SIZE][MAX_SIZE]; //邻接矩阵存储 int times[MAX_SIZE]; //记录顶点入队次数 bool SPFA(int s,int n) { //有环返回1,无环返回0 int i,v; memset(vis, 0, sizeof(vis)); memset(times, 0, sizeof(times)); queue<int>Q; //保存顶点的队列 for (i = 0; i <n; i++) dis[i] = INF; //初始化为无穷 dis[s] = 0; //源节点 Q.push(s); //源节点入队 vis[s] = 1; //源节点在队列中,所以标记为1 times[s]++; while (!Q.empty()){ //如果队列非空 v = Q.front(); //取出队列一个元素 Q.pop(); vis[v] = 0; //将该点标记为不在队列中 for (i = 0; i < n; i++) { //松弛该点的邻边 if (dis[i] > a[v][i] + dis[v]) { dis[i] = a[v][i] + dis[v]; //松弛一把更新距离 if (!vis[i]) { //如果被松弛的顶点i不在队列中 vis[i] = 1; //加入队列并标记 Q.push(i); if (++times[i] > n) { //入队次数加1次,如果入队次数超过n的话,表示有环 return 1; } } } } } return 0; }
SPFA算法有两个优化策略SLF和LLL——SLF:Small Label First 策略,设要加入的节点是j,队首元素为i,若dist(j)<dist(i),则将j插入队首,否则插入队尾; LLL:Large Label Last 策略,设队首元素为i,队列中所有dist值的平均值为x,若dist(i)>x则将i插入到队尾,查找下一元素,直到找到某一i使得dist(i)<=x,则将i出队进行松弛操作。 SLF 可使速度提高 15 ~ 20%;SLF + LLL 可提高约 50%。 在实际的应用中SPFA的算法时间效率不是很稳定。下面是SLF优化代码,使用双端队列。
#include<deque> #define INF INT_MAX/2 #define MAX_SIZE 1000 int dis[MAX_SIZE]; //保存最短距离 bool vis[MAX_SIZE]; int a[MAX_SIZE][MAX_SIZE]; //邻接矩阵存储 int times[MAX_SIZE]; //记录顶点入队次数 bool SPFA(int s,int n) { //有环返回1,无环返回0 int i,v; memset(vis, 0, sizeof(vis)); memset(times, 0, sizeof(times)); deque<int>deq; //保存顶点的队列 for (i = 0; i <n; i++) dis[i] = INF; //初始化为无穷 dis[s] = 0; //源节点 deq.push_back(s); //源节点入队 vis[s] = 1; //源节点在队列中,所以标记为1 times[s]++; while (!deq.empty()){ //如果队列非空 v = deq.front(); //取出队列一个元素 deq.pop_front(); //出队 vis[v] = 0; //将该点标记为不在队列中 for (i = 0; i < n; i++) { //松弛该点的邻边 if (dis[i] > a[v][i] + dis[v]) { dis[i] = a[v][i] + dis[v]; //松弛一把更新距离 if (!vis[i]) { //如果被松弛的顶点i不在队列中 vis[i] = 1; //加入队列并标记 if (deq.empty()||i > deq.front()) /*一点小优化*/ deq.push_back(i); else deq.push_front(i); if (++times[i] > n) //入队次数加1次,如果入队次数超过n的话,表示有环,返回1 return 1; } } } } return 0; }