图论最短路算法笔记
1 图的基本操作
1.1 图的存储
图存储有两种方法:邻接表和邻接矩阵。
- 邻接表:
g[N][N] = {};
...
memset(g, 0x3f, sizeof g);
g[u][v] = w;
- 邻接矩阵:
int head[N] = {};
memset(head, 0x3f, sizeof head);
struct edge{
int pre, to, val;
}EDGE[N];
inline void addedge(int u, int v, int w, int i){
EDGE[i] = {v, w, head[u]};
head[u] = i;
}
1.2 图的遍历
图的遍历是指从图中的任一顶点出发,对图中的所有顶点访问一次且只访问一次(访问一次,但不止用到一次)。 图的遍历操作和树的遍历操作功能相似。
2 最短路算法
图求最短路有算法:
- Floyd
- Dijkstra
- SPFA(死了)
- Bellman-Ford
- ...
2.1 Floyd
- 用途:求任意两个结点之间的最短路
- 复杂度:
- 适用:适用于任何图,不管有向无向,边权正负,但是最短路必须存在。
for(reg int k=1; k<=n; ++k)
for(reg int i=1; i<=n; ++i)
for(reg int j=1; j<=n; ++j)
if(g[i][k] + g[k][j] < g[i][j])
g[i][j] = g[i][k]+g[k][j];
以上:
2.2 Dijkstra
定义
- 用途:单源最短路径
- 复杂度:
- 适用:非负权图
将结点分成两个集合:已确定最短路长度的点集(记为
然后重复这些操作:
从
对那些刚刚被加入
直到
步骤
- 1 初始化:
源点 ,dis[N]
,book[N]
。
初始化dis(s)=0
,其他点的 均为 。
建立集合 与 。刚开始,只有 在 中。
vector<edge> e[MAXN];
int dis[MAXN], book[MAXN];
- 2 找
dis[]
最小:
开始,dis[n]
为 源点 的特殊最短路。
寻找dis[n]
中最小的节点 (可优化)。
- 3 加入集合
加入集合 ,现在 表示为最短路的部分。
- 4 借东风
与 Floyd 较为类似,就是以一个节点 作中转点,看看能不能将与周围的节点 的边 的长度减小(松弛)。
- 5 判结束
如果 集合为空集,那么全部的dis[]
都处理完毕,这时的dis[n]
为 源点 的最短路。
朴素算法(没写过,来自 oi-wiki):
struct edge {
int v, w;
};
vector<edge> e[MAXN];
int dis[MAXN], vis[MAXN];
void dijkstra(int n, int s) {
memset(dis, 0x3f, (n + 1) * sizeof(int));
dis[s] = 0;
for (int i = 1; i <= n; i++) {
int u = 0, mind = 0x3f3f3f3f;
for (int j = 1; j <= n; j++)
if (!vis[j] && dis[j] < mind)
u = j, mind = dis[j];
vis[u] = true;
for (auto ed : e[u]) {
int v = ed.v, w = ed.w;
if (dis[v] > dis[u] + w)
dis[v] = dis[u] + w;
}
}
}
堆优化
堆优化就是用堆进行优化,而朴素的算法是“扫描”一遍图找最小的点。而小根堆优先队列优化就可以快速维护最小的点,大大减少时间复杂度(
struct edge {
int pre, to, val;
}EDGE[MAXN];
int dis[MAXN], book[MAXN], head[MAXN];
inline void addedge(int u, int v, int w, int i){
EDGE[i] = {v, w, head[u]};
head[u] = i;
}
inline void dijkstra(int s){
memset(dis, inf, sizeof dis);
priority_queue <pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > heap;
heap.push({0, s});
while(!heap.empty()){
int t = heap.top().second;
heap.pop();
if(book[t])
continue;
book[t] = true;
for(reg int i=head[t]; i; i=EDGE[i].pre){
if(dis[EDGE[i].to] > EDGE[i].val+dis[t]){
dis[EDGE[i].to] = EDGE[i].to+dis[t];
heap.push({dis[EDGE[i].to], EDGE[i].to});
}
}
}
return;
}
2.3 Bellman-Ford
定义
Bellman–Ford 算法是一种基于松弛操作的最短路算法,可以求出有负权的图的最短路,并可以对最短路不存在的情况进行判断。对于边
- 用途:单源最短路径
- 复杂度:
- 适用:非负权图、负权图
2.3.1 SPFA(Bellman-Ford 算法队列优化)
定义
SPFA 算法是 Bellman-Ford 算法 的队列优化算法的别称,通常用于求含负权边的 单源最短路径,以及判负权环。 SPFA 最坏情况下时间复杂度和朴素 Bellman-Ford 相同,为
- 用途:单源最短路径
- 复杂度:
- 适用:非负权图、负权图
思想
用数组
用队列用来保存待操作的结点。每次取出队首结点
步骤
首先我们先初始化数组
队列
- 第一次循环:
出队,以 松弛,发现 到 的最短路有变化,将 加入 ,更新 ……
队列
- 第二次循环:
队头 出队,以 松弛,发现以 松弛的 的最短路有变化,将 加入 ,更新 ……
队列
- 第三次循环:
队头 出队,以 松弛,发现以 松弛的边没有变化,保持不变。
队列
- 第四次循环:
队头 出队,以 松弛,发现以 松弛的边没有变化,保持不变。
队列
- 第五次循环:
队头 出队,以 松弛,发现没有以 松弛的边,保持不变。
队列
算法结束。
所以
代码示例
struct edge {
int v, w;
};
vector<edge> e[MAXN];
int dis[MAXN], cnt[MAXN], vis[MAXN];
queue<int> q;
bool spfa(int n, int s) {
memset(dis, 0x3f, (n + 1) * sizeof(int));
dis[s] = 0, vis[s] = 1;
q.push(s);
while (!q.empty()) {
int u = q.front();
q.pop(), vis[u] = 0;
for (auto ed : e[u]) {
int v = ed.v, w = ed.w;
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
cnt[v] = cnt[u] + 1;
if (cnt[v] >= n) return false;
if (!vis[v])
q.push(v), vis[v] = 1;
}
}
}
return true;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探