南昌理工学院acm集训队
从某顶点出发,沿图的边到达另一顶点所经过的路径中,各边上权值之和最小的一条路径叫做最短路径。解决最短路的问题有以下算法,Dijkstra算法,Bellman-Ford算法,Floyd算法和SPFA算法等。
目录
最短路算法框架
最短路有五种算法,分别适用不同的情况。
单源最短路: 求一个点到其他点的最短路
多源最短路: 求任意两个点的最短路
稠密图用邻接矩阵存,稀疏图用邻接表存储。
朴素Dijkstra算法
堆优化版Dijkstra算法
优化版的Dijkstra算法是通过小根堆来找到当前堆中距离起点最短且没有确定最短路的那个点。
因为是稀疏图,所以需要用邻接表来存储。
Bellman-Ford算法
Bellman-Ford算法是通过循环 n 次,每次循环都遍历每条边,进而更新结点的距离,每一次的循环至少可以确定一个点的最短路,所以循环 n次,就可以求出 n 个点的最短路。
SPFA
SPFA算法需要图中没有负环才能使用。其实大部分正权图也是可以用SPFA算法做的,例如最上面的那到题就可以用SPFA做,效率还高于Dijkstra算法。
int spfa()
{// bool st[N]: 存第 i 个点是不是在队列中,防止存重复的点
memset(dist,0x3f,sizeof(dist));
dist[1] = 0;
queue<int> q; //存储所有待更新的点
q.push(1); // 1号点入队
st[1] = true;
while(q.size()) // 队列不空
{
int t = q.front(); //取队头
q.pop();
st[t] = false; // 代表这个点已经不在队列了,因为存在边权为负数,某个点可能会被更新多次,所以可以多次入队和出队。
for(int i = h[t]; i!=-1; i=ne[i]) // 更新 t 的所有临边结点的最短路
{
int j = e[i];
if(dist[j] > dist[t]+w[i])
{
dist[j] = dist[t] + w[i];
if(!st[j]) //如果 j 不在队列,让 j 入队
{
q.push(j);
st[j] = true; // 标记 j 在队中
}
}
}
}
if(dist[n] == 0x3f3f3f3f) return -1; // 不存在最短路
return dist[n];
}
SPFA判断负环
什么是负环呢? 下图左边的2——>3——>4就是一个负环,因为转一圈后的距离是负的,右图的 1 结点是应该自环,也属于负环。
int spfa()
{
queue<int> q;
for(int i=1; i<=n; i++) //将所有结点入队
{
st[i] = true;
q.push(i);
}
while(q.size()) // 队列不空
{
int t = q.front(); //取队头
q.pop();
st[t] = false; // 代表这个点已经不在队列了
for(int i = h[t]; i!=-1; i=ne[i]) // 更新 t 的所有临边结点的最短路
{
int j = e[i];
if(dist[j] > dist[t]+w[i])
{
dist[j] = dist[t] + w[i];
cnt[j] = cnt[t] + 1; // t到起点的边数+1
if(cnt[j] >= n) return true;// 存在负环
if(!st[j]) //如果 j 不在队列,让 j 入队
{
q.push(j);
st[j] = true; // 标记 j 在队中
}
}
}
}
return false;// 不存在负环
}
Floyd算法
Floyd算法是基于动态规划的,从结点 i 到结点 j 的最短路径只有两种:
1、直接 i 到 j
2、i 经过若干个结点到 k 再到 j
对于每一个k,我们都判断 d[i][j] 是否大于 d[i][k] + d[k][j],如果大于,就可以更新d[i][j]了。
void floyd()
{
for(int k=1; k<=n; k++)
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
三次循环完之后,遍历完所有的 k 后,d[i][j] 存的就是 i——>j的最短路了。