有关SPFA,Dijkstra和l链式前向星
今天写了道模板题
P4779 【模板】单源最短路径(标准版)
虽然只道简单的图论模板题,但却包含了两种图论常用的算法SPFA和Dijkstra,也有一种竞赛常用的数据结构链式前向星。
首先来介绍一下链式前向星吧。
链式前向星是前向星的一种优化,前向星是什么我也不知道
总的来说链式前向星可以在较小的空间复杂度中储存下一张图,因为它不像邻接表那样会占用无用的空间
接下来讲讲如何实现
int head[500005],cnt = 1;//cnt代表着边的数量+1
struct edge {
int nex, to, l;//nex表示的是上一条以i为起点的边在边数组a中的位置,to代表这条边的终点,l为长度
}a[5000005];
首先我们要定义一个head数组。
这个head数组用处很大,同时也有点难理解
实际上用大白话说就是存储以第i点为起点的边在边的数组中最后出现的位置
这样我们枚举边的时候只要知道这条边的起点在哪里就能知道以i为起点的最后一条边在边的数组中出现的位置
那我们如何实现这样的操作呢
我们定义一个加边的函数
void addedge(int start, int end, int l) {//start是边的起点,end为边的终点,l为边的长度 a[cnt].nex = head[start];//注释太多了放在下面 a[cnt].to = end; a[cnt].l = l; head[start] = cnt++; }
首先我们知道head[i]存储的是第i条边在a[ ]中最后出现的位置
南蛮我们就可以让当前的边的nex指向这个位置,就可以保存了以i为起点的上一条边的位置
之后两个没什么好说的
最后更新一下i最后出现的位置,就ok了
最后就是如何遍历,废话不多说,直接上代码
for (int i = head[u]; i != 0; i = a[i].nex) //u起点
首先我们让i得到起点在边集中的位置,然后再让i得到上一条边的位置便可以完成遍历
当遍历到最后一条以i为起点的边时它的上一条边的位置nex就是0,于是跳出循环。
好的那么我们现在就懂了链式向前星,可以开始看看SPFA了
SPFA
话不多说直接上代码
#include<iostream> #include<queue> #include<algorithm> using namespace std; int head[500005],dis[500000]; int cnt = 1; bool book[500005];//判断点是否在队列中 struct edge { int nex, to, l; }a[5000005]; void addedge(int start, int end, int l) { a[cnt].nex = head[start]; a[cnt].to = end; a[cnt].l = l; head[start] = cnt++; } int main() { int n, m, start; cin >> n >> m >> start; for (int i = 1; i <= m; i++) { int a, b, c; cin >> a >> b >> c; addedge(a, b, c);//添加边没什么好说的 } int j31 = pow(2, 31) - 1; for (int i = 1; i <= n; i++) dis[i] = j31 ;//初始化为很大 dis[start] = 0;//起点到起点的距离为0 queue<int> q; q.push(start); while (!q.empty()) { int u = q.front(); q.pop(); book[u] = 0;//点不在队列了 for (int i = head[u]; i != 0; i = a[i].nex) {//枚举边 int now = dis[a[i].to]; if (now > dis[u] + a[i].l) {//如果这个点到初始的起点的位置比从u点到这个点的路径距离大就更新一下 dis[a[i].to] = a[i].l + dis[u]; if (!book[a[i].to]) {//点不在队中便把这个点入队 q.push(a[i].to); book[a[i].to] = 1; } } } } for (int i = 1; i <= n; i++) cout << dis[i] << " "; }
我们首先定义一个dis数组,用来储存起点到这个点位置的最小值,首先初始化为很大,但起点到起点的距离为0
然后用book数组判断点是否在队列中,如果已经在队列中我们便无需再加入队中了,只需要更新它dis的值便好了,避免重复操作
通过这样的操作我们枚举了每一条经过起点的路径之后便可以算出每一个起点到其他点的距离,
如果到不了便输出2^31-1
当然这样的枚举会使得时间复杂度较高,所以在一bhu些情况下会超时所以就需要用到Dijkstra,但是必须在边权不为负的情况之下
Dijkstra
话也不多说直接上代码
#include<iostream> #include<algorithm> #include<cmath> using namespace std; int vis[50000], head[500005]; long long dis[50000];//用来记录是否已经到过这个点 int n, m, cnt = 1 , start; struct edge { int nex, to, l; }a[500005]; void addedge(int at,int to, int l) { a[cnt].nex = head[at]; a[cnt].to = to; a[cnt].l = l; head[at] = cnt++; } int main() { int INF = pow(2, 31) - 1; cin >> n >> m >> start; for (int i = 1; i <= m; i++) { int a, b, c; cin >> a >> b >> c; addedge(a, b, c); } for (int i = 1; i <= n; i++) dis[i] = INF; dis[start] = 0; while (vis[start] == 0) {//当所有能到达的点已经被走完之后跳出循环,因为start没有办法再更新了 vis[start] = true; int mi = INF; for (int i = head[start]; i != 0; i = a[i].nex) { if (dis[a[i].to] > dis[start] + a[i].l&&!vis[a[i].to]) { dis[a[i].to] = dis[start] + a[i].l; } } for (int i = 1; i <= n; i++) {//遍历所有的点当这个点到起点的距离最短同时没有到达过的点拉进循环中 if (!vis[i] && mi > dis[i]) { mi = dis[i]; start = i; } } } for (int i = 1; i <= n; i++) cout << dis[i] << " "; }
乍一看似乎和SPFA没有什么不同(其实区别还是很大的)
首先我们把book数组换成了vis数组
总的来说Dijkstra算法基于一种贪心的策略,我们每一次进行完松弛操作之后
我们再用到起点最短的点同时还没有到达过的点对其他的点进行松弛
因为若起点经过很多个点到某个终点的路径最短,那么起点到这些中转点的距离也一定是最短的,
因为如果不是最短的话就可以通过这条最短的路径来松弛掉,可能我讲的不太直观
有一个视频可以很好的表现出来 点这里
大概就是这么多了,不过Dijkstra还可以通过各种堆来优化
具体这么实现我还没学学了再说
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix