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;     
}

  

posted @ 2016-04-18 21:16  曹孟德  阅读(336)  评论(0编辑  收藏  举报