最短路算法
Dijkstra朴素
用途:
求解非负权图中单源最短路问题
思路:
维护一个已确定最短路径的顶点集合
初始时,
重复以下步骤直到
- 从
中选出不在 中且距离最小的顶点 ,将 加入 。 - 遍历
的所有邻接顶点 ,如果dist[u] + w(u,v) < dist[v]
,则更新dist[v] = dist[u] + w(u,v)
,其中 是边 的权值。
代码:
int g[N][N], dist[N];
bool st[N];
int dijkstra()
{
memset(dist,0x3f,sizeof dist);
dist[1] = 0;
for (int i = 0;i < n;i++)
{
int t = n + 1; //这样做是让dist[t]初始为无穷,方便找到要加入集合的那个点
for (int j = 1;j <= n;j++)
if (!st[j] && dist[t] > dist[j]) //找到在所有未访问过的节点中,dist值最小的节点t。这个节点是下一个要访问的节点。
t = j;
st[t] = 1;
if (t == n) break; //如果t等于n,那么说明从1到n的最短路径已经找到了,循环可以结束
for (int j = 1;j <= n;j++)
dist[j] = min(dist[j], dist[t] + g[t][j]); //更新所有未访问过的节点j的dist值
}
return dist[n];
}
Dijkstra堆优化
思路
- 将未访问的节点放入一个堆中(通常使用小根堆)
- 选取堆顶元素作为当前操作的节点,将其从堆中移除
- 对于当前节点的每一个邻居节点,如果新的路径长度比原先的更短,就更新邻居节点的距离,然后将它插入堆中
代码
int e[N], ne[N], idx, h[N], w[N];
int n,m;
int dist[N];
bool st[N];
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
priority_queue<PII, vector<PII>, greater<PII>> q;
q.push({0,1});
while (!q.empty())
{
auto t = q.top();
q.pop();
int v = t.second;
if (st[v]) continue; //已经在集合中就直接跳过
st[v] = 1; //此时加入集合
for (int i=h[v];~i;i=ne[i]) //邻接表遍历相邻结点
{
int j = e[i];
if (dist[j] > dist[v] + w[i])
{
dist[j] = dist[v] + w[i]; //松弛
q.push({dist[j], j}); //加入堆
}
}
}
return dist[n];
}
bellman-ford
用途
解决存在负权边的单源最短路径问题
基本思路
不断地松弛每条边来逐步缩小起点到各个节点的距离
给定一个加权有向图
-
对于每个节点
,初始化距离数组 为正无穷大,除了初始节点 ,这里将 设置为 。 -
对于所有的节点
和 ,如果存在一条从 到 的边 ,更新distance[v] = min(distance[v], distance[u] + w(e))
。 -
重复执行第2步,直到没有任何一条边可以使得
的值发生变化。 -
根据
判断是否存在从 到 的路径,如果存在输出该路径,否则说明不存在从 到 的路径。
代码
struct
{
int a,b,w;
}edge[M];
int n,m,k;
int dist[N], backup[N];
int bellman_ford()
{
memset(dist,0x3f,sizeof dist);
dist[1] = 0;
for (int i=0;i<k;i++) //k是拓展的层数,如果要全图的距离,遍历n次即可
{
memcpy(backup, dist, sizeof dist); //每次只从上一层拓展,不然k没意义
for (int j=0;j<m;j++)
{
int a = edge[j].a, b = edge[j].b, w = edge[j].w;
dist[b] = min(dist[b], backup[a] + w);
}
}
return dist[n];
}
spfa -
用途:
- 解决存在负权边的单源最短路径问题,是Bellman-Ford算法的一种优化版本
- 判断是否存在负环
基本思路
将起点放入队列中,然后不断从队列中取出节点,遍历其所有邻居节点,并尝试使用该节点更新邻居节点的距离值。如果某个邻居节点的距离值发生了变化,则将其加入队列中。这个过程会重复进行多次,直到队列为空为止。
代码
求最短路:
int e[N], ne[N], idx, h[N], w[N];
int dist[N];
int n,m;
bool st[N];
int q[N], tt,hh;
int spfa()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
q[tt++] = 1;
st[1] = 1;
while (hh <= tt)
{
int t = q[hh++];
st[t] = 0;
for (int i=h[t];~i;i=ne[i])
{
int j = e[i];
if (dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
if (!st[j])
{
q[tt++] = j;
st[j] = 1;
}
}
}
}
return dist[n];
}
判断负环:
int e[N], ne[N], idx, w[N], h[N];
int dist[N], q[N], tt , hh;
int n,m;
bool st[N];
int cnt[N];
bool spfa()
{
memset(dist, 0x3f, sizeof dist);
for (int i=1;i<=n;i++)
{
q[tt++] = i;
st[i] = 1;
}
while (hh <= tt)
{
int t = q[hh++];
st[t] = 0;
for (int i=h[t];~i;i=ne[i])
{
int j = e[i];
if (dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
if (!st[j])
{
q[tt++] = j;
st[j] = 1;
cnt[j] = cnt[t] + 1; //从源点到j经过的边数+1
}
if (cnt[j] >= n) return true; //边数超过n就代表存在负环
}
}
}
return false;
}
Floyd
用途
解决任意两点间最短路径问题
基本思路
动态规划
给定一个加权有向图
-
对于任意两个节点
和 ,初始化它们之间的距离 为 到 的直接距离,如果不存在直接边,则距离为正无穷大。 -
对于每个节点
,尝试使用 节点作为中介点来缩短 到 的距离,即d[i][j] = min(d[i][j], d[i][k] + d[k][j])
。
重复执行第2步,直到所有节点之间的距离都被计算出来。
代码
int d[N][N];
int 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]);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?