SPFA算法
用途
用于解决单源最短路径问题,可以处理含有负边权边的图(若存在源点可达负环则无解)。
算法描述
SPFA算法是在Bellman-Ford算法基础上改进得来的 (关于BF算法可参考 《Bellman-Ford算法》)。BF算法时间复杂度较高,原因在于每一次循环都需要遍历图中所有边,其中包含了大量的重复遍历,这就会使算法效率大大降低。SPFA算法依据的原理是:只有当某个结点到源点的最短距离d[i]改变后,由结点i出发可达的结点j与源点的最短距离d[j]才可能会被改变。由此可以对BF算法进行优化得到期望时间复杂度为 O ( k E ) O(kE) O(kE) (k为常数,E为边数)的SPFA算法,其主要步骤如下:
- 建立队列q,将源点入队;
- 队首结点u出队,遍历u的所有出边 u->v 进行松弛操作,如果 d[u] + length[u->v] < d[v], 则用较小值更新d[v],若此时v不在队列中,则将其入队;
- 当队列非空时,重复步骤2,同时每次操作要判断是否有某个结点的入队次数大于 n - 1,若有则说明图中存在从源点可达的负环,结束算法并返回false;
- 所有最短距离优化完毕,结束算法并返回true。
代码实现
const int maxn = 1000;
const int INF = 1000000000;
struct node
{
int v, l;
};
vector<node> adjL[maxn]; // 邻接表
int n; // 结点个数
int d[maxn]; // 结点i到源点的最短距离
int num[maxn]; // 结点i的入队次数
bool inq[maxn]; // 结点i是否已入队
bool SPFA(int s)
{
for (int i = 0; i < n; i++) // 初始化
{
num[i] = 0;
d[i] = INF;
inq[i] = false;
}
d[s] = 0; // 源点到自身距离为0
queue<int> q;
q.push(s); // 源点入队
num[s]++;
inq[s] = true;
while (!q.empty()) // 队非空一直循环
{
int u = q.front(); // 队首结点出队
q.pop();
inq[u] = false;
for (int i = 0; i < adjL[u].size(); i++) // 遍历所有出边
{
int v = adjL[u][i].v;
int l = adjL[u][i].l;
if (d[u] + l < d[v]) // 松弛
{
d[v] = d[u] + l;
if (!inq[v])
{
q.push(v);
inq[v] = true;
num[v]++;
if (num[v] > n - 1) // 入队次数超过 n - 1,说明存在源点可达负环,返回false
return false;
}
}
}
}
return true;
}