最短路
1 算法描述
在一图中,从一点出发,沿图的边走到另一点所经过的路径中,各边上权值和最小的路径,叫做最短路径。最短路算法就是求解最短路径问题的算法。
其中,单源最短路径指从图中某一点到另外所有点的最短路径;多源最短路径指从图中每一点到另外所有点的最短路径。
2 四大最短路算法
2.1 Floyd 算法
Floyd 是一种多源最短路算法,其思想是动态规划。
设
若最短路径经过点
若最短路径不经过点
综上,
实际中,常将 dp 数组降至二维使用
Floyd 的时间复杂度为
核心代码如下(使用邻接矩阵存图):
int a[Maxn][Maxn];
for(int k = 1; k <= n; k++)//Floyd
{
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= n; j++)
{
a[i][j] = min(a[i][j], a[i][k] + a[k][j]);
}
}
}
2.2 dijkstra 算法
dijkstra 是一种单源最短路算法,适用于边权值为正的情况。
它的思想是从源点
算法步骤如下:
设
第一步,将
第二步,选一个
重复第二步,直到所有点都被标记。
dijkstra 的正确性说明如下:
每次选取
朴素 dijkstra 的时间复杂度为
观察上述算法步骤,发现第二步中寻找最小值的部分可以用堆来优化,可以将时间复杂度降至
堆优化算法如下(使用链式前向星存图):
int s, dis[Maxn], vis[Maxn];
priority_queue <pair<int, int> > q;//堆
void dijkstra()
{
for(int i = 1; i <= n; i++)//初始化
{
dis[i] = 2e9;
}
dis[s] = 0;
q.push(make_pair(0, s));
while(!q.empty())//dijkstra
{
int x = q.top().second;
q.pop();
if(vis[x]) continue;
vis[x] = 1;
for(int i = head[x]; i; i = edge[i].nxt)
{
if(dis[edge[i].to] > dis[x] + edge[i].w)
{
dis[edge[i].to] = dis[x] + edge[i].w;
q.push(make_pair(-dis[edge[i].to], edge[i].to));
}
}
}
}
3.3 Bellman - Ford 及 SPFA 算法
Bellman - Ford 是一种单源最短路算法,它可以适用于带负权边的图。SPFA 则是 Bellman - Ford 的队列优化算法。
Bellman - Ford 的算法流程如下:
以任意顺序考虑图的边,沿着各条边进行松弛操作。即对于边
(其中
最后再对各条边进行松弛操作,如果对于边边
Bellman - Ford 的正确性说明如下:
图的任意一条最短路径既不能包含负权回路(无解),也不会包含正权回路(不是最优解),因此它最多包含
从源点
第一轮松弛,就生成了距离
Bellman - Ford 时间复杂度为
核心代码如下(使用邻接矩阵存图):
int dis[Maxn];
bool bellman_ford(int s)
{
for(int i = 1; i <= n; i++)
dis[i] = INF;
dis[s] = 0;
for(int i = 1; i <= n - 1; i++)//松弛
for(int j = 1; j <= m; j++)
if(dis[edge[j].u] + edge[j].w < dis[edge[j].v]])
dis[edge[j].v] = dis[edge[j].u] + edge[j].w;
for(int j = 1; j <= m; j++)//判负权环
if(dis[edge[j].u] + edge[j].w < dis[edge[j].v]])
return 0;
return 1;
}
Bellman - ford 算法效率低在无效松弛操作太多,而 SPFA 使用队列优化了 Bellman - Ford 的效率。
SPFA 算法流程如下:
设一队列
从队列中取出队首元素
重复上述过程直到队列为空。
SPFA时间复杂度为
核心代码如下:
int dis[Maxn], vis[Maxn], t[Maxn];
queue <int> q;
bool SPFA(int s)
{
for(int i = 1; i <= n; i++)
dis[i] = INF;
dis[s] = 0;
q.push(s);
vis[s] = 1;
while(!q.empty())
{
int now = q.front();
q.pop();
if(++t[now] == n)
{
return 0;
}
vis[now] = 0;
for(int i = 0; i < E[now].size(); i++)
{
int v = E[now][i].first;
if(dis[v] > dis[now] + E[now][i].second)
{
dis[v] = dis[now] + E[now][i].second;
if(vis[v] == 1) continue;
vis[v] = 1;
q.push(v);
}
}
}
return 1;
}
3 总结
四种算法各有优缺点,需要根据题目需求来决定使用哪种。
Floyd | dijkstra | Bellman - Ford | SPFA | |
---|---|---|---|---|
源点 | 多源 | 单源 | 单源 | 单源 |
空间复杂度 | ||||
时间复杂度 | ||||
能否有负权边 | 可以 | 不可以 | 可以 | 可以 |
能否判断负权回路 | 不可以 | 不可以 | 可以 | 可以 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律