[算法学习记录] [更新中]最短路

news/2025/3/19 19:07:11/文章来源:https://www.cnblogs.com/Pingjiadoge/p/18771051

顾名思义,最短路算法,就是求一个图中所有的点距某一个点的最短距离,常见的有Dijkstra算法、Bellman-Ford算法、Johnson算法与Floyd算法。

Dijkstra

Dijistra算法实际上是一种由贪心与动态规划结合的算法,我们每次都贪心地选择到某个点的最近距离,又动态地更新着距离数组d的数据,直到完成对每一个点的操作。
但它也存在着缺陷,Dijkstra算法并不能处理负权图,因为这与其核心算法贪心相悖,因为如果存在负权边,按照贪心的写法就只能选择那条负权边,从而陷入死循环。

  1. 算法拆解
    为了减少时间复杂度,我们把每一个父节点及其邻接表存入堆(小根堆)中,这样堆顶的元素就始终是该节点的子节点中权值最小的,为了实现小根堆,我们还需要重载小于号,代码如下:
struct Edge
{int x, w;//x是节点的编号,w是节点的权值bool operator < (const Edge&u)const{if(w != u.w) return w > u.w;//由于堆是利用小于号来实现大根堆(堆顶是最大值)的,所以我们只要更改小于号的规则,就能实现小根堆。}
}

为了避免重复操作,我们可以创建一个bool数组(或创建bitset,两者的作用在这儿是相同的),来标记已操作过的父节点;由于我们要得到的是最短路,为了避免出错,我们应该把d距离数组的值都初始化为当前使用的数据类型的最大值。完成以上操作后,我们就该对每个点进行操作了,完成操作后,再把当前节点的子节点存入堆中,以便实现对所有点的操作,示例代码如下:

void dijkstra(int st)
{for(int i = 1;i <= n;i++) d[i] = inf;//首先对d(每个点到起点的距离)数组进行初始化,其中inf是long long的近似最大值priority_queue<Edge> pq;pg.push({st, d[st] = 0});bitset<N> vis;//N,是点个数的上限while(pq.size()){int x = pq.top(),pq.pop();if(vis[x])continue;//操作过的节点,直接跳过for(auto &t : g[x]){int v = t.x, w = t.w;if(d[x] + w < d[v]){d[v] = d[x] + w;//找到更小的路径,更新pq.push({v,d[v]});//把子节点放入堆中}}}//如果堆非空,也就是说没有遍历所有点,就继续操作
}

例题1 星码Starrycoding P70 【模板】最短路(2)
本题是dijkstra算法的模版题,依照上文所说的思路写出代码即可,代码如下:

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 2e5 + 5;
const ll inf = 2e18;
int n, m;
ll d[N];
//用一个数组存储各个点到起点的距离
struct edge
{ll x, w;//x是某一个点所连接的点,w是权值bool operator < (const edge& u)const{if (w != u.w)return w > u.w;//堆默认是以<实现大根堆的,所以我们用>就可以实现小根堆}//重载<以实现小根堆,最短路的核心思想就是贪心和dp,既然求最短路,那么就需要选权值(距离)最小的
};
vector<edge> g[N];
//创建邻接表,存储每个点的子节点与每条边的权值
void dijkstra(int st)
{for (int i = 1; i <= n; i++) d[i] = inf;//首先将所有节点距起点的距离为(ll的)最大值,默认除起点外的点都无法到达priority_queue<edge>pq;//使用堆(小根堆)来实现权值最小点的快速寻找bitset<N>vis;//标记以处理过的父节点pq.push({ st,d[st] = 0 });//把起点放入堆中while (pq.size()){int x = pq.top().x; pq.pop();if (vis[x])continue;//跳过已经入堆的节点,防止重复vis[x] = true;//将该点标记为已走过for (const auto &t : g[x]){int v = t.x, w = t.w;if (d[x] + w < d[v]){d[v] = d[x] + w;pq.push(edge{ v,d[v] });}//遍历x这一点的所有子节点,如果有一个子节点可以让距离缩短,那么就更新距离,并把更新后的点放入堆中}}//如果堆非空就一直进行
}void solve()
{cin >> n >> m;for (int i = 1; i <= m; i++){ll x, y, w; cin >> x >> y >> w;g[x].push_back({ y,w });}dijkstra(1);cout << (d[n] == inf ? -1 : d[n]) << "\n";//如果最后一个点的值仍是最大值,说明不存在从1到n的最短路径
}int main()
{ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);int _ = 1;while (_--) solve();return 0;
}

例题2 星码Starrycoding P113 小鱼吃虾米
本题仍是一个明显的最短路问题,但不一样的是我们需要求的是其它点到某一点的距离,与dijkstra算法正好想反,如果直接对每一点进行求解,时间复杂度将会非常之大,所以我们不妨把思路逆转过来,我们仍然把它看做一个从某一点到其它所有点的最短距离的题,为了做到这一点,我们需要把图的指向全都翻转过来(只需要在读入邻接表时倒转一下即可),代码如下。

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const ll N = 1e4+5,inf = LLONG_MAX;
ll v[N],d[N],n,m,k;struct Edge
{ll x,w;bool operator < (const Edge&u)const{if(w != u.w)return w > u.w;}	
};//重载小于号,实现小根堆 
vector<Edge> g[N];
//邻接表 void dijkstra(int st)
{for(int i = 1;i<=n;i++)d[i] = inf;bitset<N> vis;priority_queue<Edge> pq;pq.push({st,d[st] = 0});while(pq.size()){int x = pq.top().x;pq.pop();if(vis[x])continue;vis[x] = true;for(auto &t : g[x]){int v = t.x,w = t.w;if(d[x] + w < d[v]){d[v] = d[x] + w;pq.push({v,d[v]});}}}
}void solve()
{cin >> n >> m >> k;for(int i = 1;i<=k;i++)cin >> v[i];for(int i = 1;i<=m;i++) g[i].clear();//测试样例不止一个,所以别忘了清空邻接表。 while(m--){int a, b, c;cin >> a >> b >> c;g[b].push_back({a,c});}dijkstra(1);ll ans = inf;for(int i = 1;i <= k;i++)	ans = min(ans, d[v[i]]);//求所有路径中的最小值 cout << (ans==inf ? -1 : ans) << "\n";
}int main()
{ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);int _;cin >> _;while(_--)solve();return 0;
}

Bellman-Ford

相比于Dijkstra算法,Bellman-Ford算法的最大优点就是能够处理存在负权的图,它能够在求得最短路的同时检测出负权环。
Bellman-Ford算法的核心思想就是“松弛”,比如下面这样。
松弛操作
所以,如果一个图如果在进行n-1次松弛操作后仍然可以进行松弛操作,说明该图一定存在负权环,代码实现如下:
星码Starrycoding P97

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const ll N = 1e4+5,inf = LLONG_MAX;
ll d[N], n, m;
//d数组就是每个点到起点的距离 
struct Edge
{ll x,w;	
}; 
vector<Edge> g[N];
//存储子节点以及其权值的邻接表 
void solve()
{cin >> n >> m;while(m--){int x,y,z;cin >> x >> y >> z;g[x].push_back({y,z});//读取邻接表 }for(int i = 2;i <= n;i++)d[i] = inf;//将除第一个点外的其它点到第一个点的距离设为ll的最大值,以防止后续出错 bool circle = false;//负权的判断 for(int i = 1;i<=n;i++){circle = false;for(int j = 1;j<=n;j++){for(const auto &t : g[j]){int u = t.x,w = t.w;if(d[j] + w < d[u]){d[u] = d[j] + w;circle = true;}}}}//有n个点的图有n-1条边,所以如果无负权环,在n-1次松弛操作后,就不能再进行松弛操作了。 if(circle) cout << -1 <<"\n";else {for(int i = 1;i<=n;i++)cout << d[i] <<" ";cout << "\n";}
}int main()
{ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);int _ = 1;while(_--)solve();return 0;
}

松弛过程中的暴力枚举会导致时间复杂度较高,所以我们可以对该算法进行队列优化,以便显著改善某些场景下的时间复杂度。

SPFA算法(Bellman-Ford算法的路径优化版本)

SPFA算法可以显著改善某些只能用Bellman-Ford算法解决的问题大的时间复杂度,与Dijkstra算法的优化版本类似,SPFA算法是利用队列来减少冗余操作的,只处理操作过的节点的子节点。代码实现如下:

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e5+5,inf = 2e18;
ll n, m, d[N], cnt[N]; struct Edge
{int x,w;
};
vector<Edge> g[N];
//创建邻接表 
bool SPFA(int st)
{for(int i = 2;i <= n;i++) d[i] = inf;queue<int> q;bitset<N> inq;//标记顶点是否在队列中 d[st] = 0;q.push(st);while(q.size()){int x = q.front();q.pop();inq[x] = false;//弹出队列元素,并将该节点标记为false; for(const auto &t : g[x]){int u = t.x,w = t.w;if(d[x]+w<d[u]){d[u] = d[x] + w;cnt[u] = cnt[x] + 1;//cnt是记录到i的路径边数,如果要经过超过n-1条边(n个顶点,n-1条边)才能到达,说明存在负权环 if(cnt[u]>=n) return false;if(!inq[u])q.push(u),inq[u] = true;//将未操作过的节点放入队列中 }} //遍历该节点的子节点并进行松弛操作 }return true;
}
//SPFA算法实现返回true说明无负权环,否则有负权环
void solve()
{cin >> n >> m;while(m--){int x, y, z;cin >> x >> y >> z;g[x].push_back({y, z});}if(SPFA(1)) for(int i = 1;i <= n;i++) cout << d[i] << " ";else cout << -1 << " ";}int main()
{ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);int _ = 1;while(_--)solve();return 0;
}

Johnson最短路径

Floyd

Floyd算法与Dijkstra算法的不同之处就在于Floyd算法可以求任意两点间的最短路,相比Dijkstra算法更加灵活。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/901652.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

day6-static静态变量

静态变量/静态方法 被static修饰 工具类静态方法不能调用非静态。 非静态可以访问所有。 继承一个子类只能继承一个父类,可以多层继承,最大的父类为Objectcoding 练习:更好的方法:双指针不浪费空间

SpringBoot引入JWT实现用户校验

一JWT JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在网络应用环境间安全地传输信息。这些信息以JSON对象的形式存储在令牌中,并且可以被签名和加密。JWT通常用于身份验证和信息交换主要用途 身份验证:当用户登录成功后,服务器会生成一个JWT并返回给客户端。之后…

mcp-playwright测评

mcp-playwright介绍 mcp-playwright是一个使用 Playwright 提供浏览器自动化能力的模型上下文协议服务器。该服务器使 LLMs 能够与网页交互、截屏,并在真实的浏览器环境中执行 JavaScript。 GitHub地址:https://github.com/executeautomation/mcp-playwright。安装 npm insta…

传媒行业项目管理全解析:日事清在流程、文件、数据与风控中的一体化应用

一、关于森可文化 森可文化传媒有限公司(Senke Vision)占据了超过2800平方米的宽敞办公及拍摄场地,与众多内衣及服饰行业的上市巨头建立了不可动摇的深度合作关系。 Senke Vision汇聚了国内外顶尖的策划、设计、摄影摄像、服装搭配、化妆及品牌视觉顾问,形成了一支独一无二…

在 .NET 项目中使用 husky 完成 pre-commit 操作

将 husky 引入 .NET 项目Husky 是一个用于 Git 仓库的工具,主要用于管理 Git 钩子(hooks)。它可以帮助开发者在特定的 Git 操作(如提交、推送等)之前或之后执行自定义的脚本或命令,从而提高代码质量和团队协作效率。 主要用在前端项目中,可以通过 Husky.Net,将 Husky 的…

贸易企业数字化转型案例:基于日事清的目标管理、任务协作与流程可视化绩效优化实践

这家贸易公司如何提升内部协同效率?一、基本情况 所属行业:传统贸易行业 业务类型:国内贸易、货物及技术进出口 行业地位:拥有自己的研发人员,具备一站式解决方案能力。 合作概要:为解决组织提效,目标体系、协同体系、绩效体系的管理问题,客户与日事清达成合作,并将日…

使用nvm管理node.js版本

1.情景展示如上图所示,项目某个模块支持的node.js最高版本是17,我用的是20,所以只能降级。 2.具体分析 我现在把node.js降到16,那后续如果再需要20呢?能不能实现版本的随时切换? 3.安装nvm 我们可以使用nvm来管理node.js的版本。 下载nvm windows下载地址:https://githu…

3.19 学习记录

完成了仓库管理系统的制作,基本无bug,所展示功能都可以实现,基于 springboot 和 vue3 具体实现如下: 登录管理员页面 有仓库管理,物资管理,库存管理和统计功能仓库工作人员页面

网站测速——提升用户体验的关键

在互联网飞速发展的今天,网站已成为企业展示形象、提供服务以及用户获取信息的重要平台。而网站的速度,如同高速公路的路况,直接影响着用户的访问体验和满意度。因此,网站测速成为了网站运营和维护中不可或缺的关键环节。 网站速度对企业来说真的那么重要吗?​ 网站测速的…

表单和载荷的区别,以及python和js在处理json时的空格问题。

1、在 传载荷的时候,用json= 这样传底层会调用json.dumps来转换,如:response = requests.post(url,headers=headers,params=params,data=json.dumps(data.separators=(,":")) 2、在 传表单的时候,用data= 如:response = requests.post(url,headers=headers,…

荣誉| 触想TPC07-WIPC工控机荣获2025控制产品“新质”奖!

3月13日,第23届中国自动化与数字化“新质奖”评选颁奖典礼在无锡举办,触想自主研发的TPC07-WIPC工控机以卓越性能、灵活扩展和可靠性备受关注,获得2025控制产品“新质”奖。△ 触想代表朱涛(左四)上台领奖TPC07-WIPC系列工控机是触想面向边缘计算、机器视觉、AI智能等场景推…

汉诺塔游戏 | 数学益智游戏②

依托国产的 AI 豆包完成的网页端的汉诺塔益智游戏。前情概要 AI 辅助设计的一款数学益智游戏,汉诺塔游戏。 汉诺塔游戏body0 {font-family: Arial, sans-serif; text-align: left; background-color: #f4f4f9; display: flex; flex-direction: column; align-items: center; }…