继续图论的单源最短路径算法实现,上次学习了Bellman-Ford算法,知道了它的复杂度比较高,主要消耗在松弛操作有点冗余。所以,就有人对其进行优化,西南交通的段凡工前辈提出了这个SPEA算法。在Wiki上看到,有人把其称为Shortest Path Finding Algorithm,也不知道具体是怎样的,不过看在其速度蛮快的份上,我就用Faster了。复杂度快达O(kE)。
其中,优化在哪里呢,如下:
SPFA对Bellman-Ford算法优化的关键之处在于意识到:只有那些在前一遍松弛中改变了距离估计值的点,才可能引起他们的邻接点的距离估计值的改变。因此,算法大致流程是用一个队列来进行维护,即用一个先进先出的队列来存放被成功松弛的顶点。初始时,源点s入队。当队列不为空时,取出队首顶点,对它的邻接点进行松弛。如果某个邻接点松弛成功,且该邻接点不在队列中,则将其入队。经过有限次的松弛操作后,队列将为空,算法结束。SPFA算法的实现,需要用到一个先进先出的队列 queue 和一个指示顶点是否在队列中的标记数组mark。为了方便查找某个顶点的邻接点,提高效率,图采用临界表存储。
Wiki上谈到了SPEA的优化,如下:
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的算法时间效率不是很稳定,为了避免最坏情况的出现,通常使用效率更加稳定的Dijkstra算法。
废话不多说,代码如下:
#include <iostream>
#include <queue>
//Vertex number no more than MAX_INT, because ip is int tpye in initialize.
#define MAXEDGE 100
#define MAXVERTEX 15
#define MAX_INT 0x7FFFFFFF
#define AL_END -1
using namespace std;
//For adjacency list.
struct Edge
{
int next;
int vertex;
long weight;
};
class AlGraph
{
public:
int source;
int edgeNum;
long distance[MAXVERTEX];
bool hasNegativeCycle;
void spea();
private:
Edge edge[MAXEDGE]; //For adjacency list.
int alVertex[MAXVERTEX]; //For adjacency list.
bool inQueue[MAXVERTEX]; //For fast speed.
queue<int> spfaQueue;
int inQueueTimes[MAXVERTEX]; //To tests negative cycles.
void initialize();
};
void AlGraph::initialize()
{
hasNegativeCycle = false;
memset(alVertex, AL_END, sizeof(alVertex));
memset(inQueue, 0, sizeof(inQueue));
memset(inQueueTimes, 0, sizeof(inQueueTimes));
fill(distance, distance + MAXVERTEX, MAX_INT);
while (!spfaQueue.empty())
{
spfaQueue.pop();
}
int ip = 0;
int source;
int destination;
int weight;
for (int i = 0; i < edgeNum; i++)
{
cin >> source >> destination >> weight;
edge[ip].next = alVertex[source];
edge[ip].vertex = destination;
edge[ip].weight = weight;
alVertex[source] = ip++;
/*
//For undirected graph.
edge[ip].next = alVertex[destination];
edge[ip].vertex = source;
edge[ip].weight = weight;
alVertex[destination] = ip++;
*/
}
}
void AlGraph::spea()
{
initialize();
distance[source] = 0;
spfaQueue.push(source);
inQueueTimes[source]++;
inQueue[source] = true;
while (!hasNegativeCycle && !spfaQueue.empty())
{
int nowvertex = spfaQueue.front();
spfaQueue.pop();
inQueue[nowvertex] = false;
long a;
long b;
for (int i = alVertex[nowvertex]; i != AL_END; i = edge[i].next)
{
a = distance[edge[i].vertex];
b = distance[nowvertex] + edge[i].weight;
if (b < a)
{
distance[edge[i].vertex] = b;
if (!inQueue[edge[i].vertex])
{
spfaQueue.push(edge[i].vertex);
inQueue[edge[i].vertex] = true;
}
inQueueTimes[edge[i].vertex]++;
if (inQueueTimes[edge[i].vertex] > edgeNum)
{
hasNegativeCycle = true;
}
}
}
}
}
int main()
{
AlGraph test;
cin >> test.source >> test.edgeNum;
test.spea();
if (!test.hasNegativeCycle)
{
for (int i = 0; i < MAXVERTEX; i++)
{
cout << "Distance to " << i << " is: " << test.distance[i] << endl;
}
}
return 0;
}