【C/C++】最短路径
BFS求无权图的最短路径
用book数组的值表示路径长度即可,省略
Floyd算法(允许负边)
- Floyd算法可以一次性求出所有节点之间的最短距离,且代码简单,但是时间复杂度达到了n^3,因此只适用于n<200的情况;
- 原理:任意两点i,j之间的距离分为两种情况:过k点和不过k点。从k=1开始操作遍历到n即可,不过很显然每次计算基本上只有对k的邻边是有效的
- 代码实现(基于邻接矩阵):
#include<bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int MAXN = 105;
int graph[MAXN][MAXN];
void floyd(int n)
{
int s=1;//求s到n的距离
for(int k=1; k<=n; k++)
for(int i=1; i<=n; i++)
if(graph[i][k] != INF)//如果相等则说明无需遍历
for(int j=1; j<=n; j++)
if(graph[i][j] > graph[i][k] + graph[k][j])
graph[i][j] = graph[i][k] + graph[k][j];
printf("%d\n",graph[s][n]);//输出结果
return ;
}
int main()
{
int n,m;
while(~scanf("%d %d",&n,&m))
{
if(n==0 && m==0) break;
memset(graph,0x3f,sizeof(graph));
while(m--)
{
int a,b,c;
scanf("%d %d %d",&a,&b,&c);
graph[a][b] = graph[b][a] = c;
}
floyd(n);
}
return 0;
}
- Floyd算法适用于邻接矩阵:由于算法是动态规划的思想,必须有一个二维数组表示点与点之间的距离,所以使用其他图的表示方法会浪费空间;
- 判断负圈:通过代码不难发现,点 i 到自己的距离并非为0,而是graph[i][i] = graph[i][k] + graph[k][i],即绕外面一圈再回来。一旦存在graph[i][i]<0,就说明有负圈存在;
Bellman-Ford算法(允许负边)
- Bellman-Ford算法解决的是单源最短路径问题,即起点s到图中每个点的最短距离;
- 原理:每一轮更新中,对于每一个点,询问它的邻居是否可以到达s点:如果可以,当前的点就可以通过邻居到达s点。不难想象,每一轮更新中至少有一个点到s的最短距离可以被确定下来,所以一共需要更新n轮,时间复杂度为O(n*m);
- 图的表示方式:如果使用邻接矩阵,遍历边的过程依然为O(n*n),并没有得到优化,因此使用数组存边或者邻接表来存图;
- 代码实现:
#include<bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int MAXN = 5e3 + 10;
struct edge{int u, w, v; } e[MAXN*2];
int pre[MAXN],d[MAXN];//pre存放前置节点,d[i]存放s到点i的距离
void Print_path(int s, int t)//打印s到t的最短路径
{
if(s == t) printf("%d",s);
else
{
Print_path(s,pre[t]);
printf(" %d",t);
}
}
void bellman(int n, int cnt)
{
int s = 1;//s为起点
memset(d,0x3f,sizeof(d));//初始化为最大值
d[s] = 0;//s到自己的距离为0
for(int k=1; k<=n; k++)
for(int i=0; i<cnt; i++)
{
int x = e[i].u, y = e[i].v;
if(d[x] > d[y] + e[i].w)
{
d[x] = d[y] + e[i].w;
pre[x] = y;
}
}
printf("%d\n",d[n]);//打印s到n的最短距离
Print_path(s,n);
printf("\n");
}
int main()
{
int n,m;
while(~scanf("%d %d",&n,&m))
{
int cnt = 0;
while(m--)
{
int a,b,c;
scanf("%d %d %d",&a,&b,&c);
e[cnt].u = a; e[cnt].v = b; e[cnt].w = c; cnt++;
e[cnt].v = a; e[cnt].u = b; e[cnt].w = c; cnt++;
}
bellman(n,cnt);
}
return 0;
}
- 判断负圈:很明显当负圈存在时,程序会一直存在距离更新,因此要判断循环次数是否超过了n,如果超过说明有负圈;
- 优化后的代码:
bool bellman(int n, int cnt)
{
int s = 1;//s为起点
memset(d,0x3f,sizeof(d));//初始化为最大值
d[s] = 0;//s到自己的距离为0
bool updata = true;//updata表示上一轮有没有更新,如果有则继续更新,否则停止更新
int k = 0;//k表示循环次数
while(updata)
{
updata = false;//当前一轮还未进行过更新
k++;
if(k>n) return false;//循环次数超过n则返回有负圈
for(int i=0; i<cnt; i++)
{
int x = e[i].u, y = e[i].v;
if(d[x] > d[y] + e[i].w)
{
updata = true;//发生了更新操作
d[x] = d[y] + e[i].w;
pre[x] = y;
}
}
}
printf("%d\n",d[n]);//打印s到n的最短距离
Print_path(s,n);
printf("\n");
return true;
}
SPFA算法(允许负边)
- SPFA是对Bellman-Ford算法的优化。在Bellman-Floyd算法每一轮的更新中,如何确定当前节点v需不需要更新?很明显,当且仅当v的邻居节点的最短路径发生变动的时候,节点v才需要更新。SPFA算法使用BFS的思想,把需要更新的节点放进队列中,当队列为空时,算法结束。
- 判断负圈:节点每进入一次队列即为当前节点更新了一次,由Bellman-Ford算法中的结论可知,当存在一个节点更新次数超过n次时,说明一定有负圈。
- 代码实现(:
//基于邻接表
#include<bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int MAXN = 1e6 + 10;
struct edge
{
int to,w;
};
vector<edge>e[MAXN];
int pre[MAXN];//记录路径
bool inq[MAXN];//是否在队列内,优化用
int Neg[MAXN];//记录循环次数判断负圈
int dis[MAXN];//记录最短距离
void print_path(int s, int t)//递归输出最短路径
{
if(s == t) {printf("%d",s); return ;}
print_path(s, pre[t]);
printf(" %d",t);
}
void print_path2(int s, int t)//非递归的路径输出,适用于极端情况
{
stack<int>ans;
while(s!=t)
{
ans.push(t);
t = pre[t];
}
printf("%d",s);
while(!ans.empty())
{
printf(" %d",ans.top());
ans.pop();
}
printf("\n");
}
bool spfa(int s, int n)
{
memset(dis,0x3f,sizeof(dis));
memset(inq,false,sizeof(inq));
memset(Neg,0,sizeof(Neg));
Neg[s] = 1;
inq[s] = true;
dis[s] = 0;
queue<int>Q;
Q.push(s);
while(!Q.empty())
{
int u = Q.front();
Q.pop();
inq[u] = false;
for(int i=0; i<e[u].size(); i++)
{
int v = e[u][i].to;
int w = e[u][i].w;
if(dis[v] > dis[u] + w)
{
dis[v] = dis[u] + w;
pre[v] = u;
if(!inq[v])
{
inq[v] = true;
Q.push(v);
Neg[v]++;
if(Neg[v] > n) return false;//返回值为false代表有负圈
}
}
}
}
printf("%d\n",dis[n]);
print_path2(s,n);
return true;
}
int main()
{
int n,m;
while(~scanf("%d %d",&n,&m))
{
for(int i=0; i<m; i++)
{
int a,b,c;
scanf("%d %d %d",&a,&b,&c);
edge t = {b,c};
e[a].push_back(t);
}
spfa(1,n);
}
return 0;
}
//基于链式前向星的SPFA算法
#include<bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int NUM = 1e6 + 10;
struct Edge
{
int to,next,w;
}edge[NUM];
int cnt;
int head[NUM];
int dis[NUM];
bool inq[NUM];
int Neg[NUM];
int pre[NUM];
void print_path(int s,int t)//打印起点s到t的最短路径
{
stack<int>ans;
while(t!=s)
{
ans.push(t);
t = pre[t];
}
printf("%d",s);
while(!ans.empty())
{
printf(" %d",ans.top());
ans.pop();
}
printf("\n");
}
void init()//前向星的初始化
{
for(int i=0; i<NUM; i++)
{
edge[i].next = -1;
head[i] = -1;
}
cnt = 0;
}
void addedge(int u, int v, int w)//前向星的加边操作
{
edge[cnt].to = v;
edge[cnt].w = w;
edge[cnt].next = head[u];
head[u] = cnt++;
}
bool spfa(int s, int n)
{
memset(inq,false,sizeof(inq));
memset(dis,0x3f,sizeof(dis));
memset(Neg,0,sizeof(Neg));
Neg[s] = 1;
dis[s] = 0;
inq[s] = true;
queue<int>Q;
Q.push(s);
while(!Q.empty())
{
int u = Q.front(); Q.pop();
inq[u] = false;
for(int i=head[u]; i!=-1; i=edge[i].next)
{
int v = edge[i].to; int w = edge[i].w;
if(dis[v] > dis[u] + w)
{
dis[v] = dis[u] + w;
pre[v] = u;
if(!inq[v])
{
Q.push(v);
inq[v] = true;
Neg[v]++;
if(Neg[v] > n) return false;
}
}
}
}
printf("%d\n",dis[n]);
print_path(s, n);
return true;
}
int main()
{
int n,m;
while(~scanf("%d %d",&n,&m))
{
init();
while(m--)
{
int a,b,c;
scanf("%d %d %d",&a,&b,&c);
addedge(a, b, c);
}
spfa(1, n);
}
return 0;
}
Dijkstra算法(无法求负边)
- Dijkstra算法应用了贪心的思想,即从起点开始抄近路走。类似于多米诺骨牌,即从起点开始推倒骨牌,当节点第一次被到达时最短路径被确定。
- 代码借助STL中的优先队列完成,每次取出到S距离最短的节点来模拟多米诺骨牌模型。
//基于邻接表
#include<bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int NUM = 1e5;
struct edge
{
int from, to, w;
edge(int a, int b, int c)
{
from = a;
to = b;
w = c;
}
};
vector<edge>e[NUM];
struct s_node
{
int id, n_dis;
s_node(int b, int c)
{
id = b;
n_dis = c;
}
bool operator < (const s_node & a) const
{
return n_dis > a.n_dis;
}
};
int dis[NUM];
bool done[NUM];
int pre[NUM];//记录前驱结点
void print_path(int s, int t)
{
stack<int>ans;
while(s != t)
{
ans.push(t);
t = pre[t];
}
printf("%d",s);
while(!ans.empty())
{
printf(" %d",ans.top());
ans.pop();
}
printf("\n");
}
void dijkstra(int s,int n)
{
memset(dis, 0x3f, sizeof(dis));
memset(done, false, sizeof(done));
dis[s] = 0;
priority_queue<s_node>Q;
Q.push(s_node(s, dis[s]));
while(!Q.empty())
{
s_node u = Q.top();
Q.pop();
if(done[u.id])
continue;
done[u.id] = true;
for(int i=0; i<e[u.id].size(); i++)
{
edge y = e[u.id][i];
if(done[y.to])
continue;
if(dis[y.to] > y.w + u.n_dis)
{
dis[y.to] = y.w + u.n_dis;
Q.push(s_node(y.to, dis[y.to]));
pre[y.to] = u.id;
}
}
}
printf("%d\n",dis[n]);
print_path(s, n);
}
int main()
{
int n,m;
while(~scanf("%d %d",&n,&m))
{
if(n==0 && m==0) break;
for(int i=1; i<=n; i++)
e[i].clear();
while(m--)
{
int a,b,c;
scanf("%d %d %d",&a,&b,&c);
e[a].push_back(edge(a,b,c));
e[b].push_back(edge(b,a,c));
}
dijkstra(1, n);
}
return 0;
}
【C/C++】最短路径的更多相关文章
- Johnson 全源最短路径算法
解决单源最短路径问题(Single Source Shortest Paths Problem)的算法包括: Dijkstra 单源最短路径算法:时间复杂度为 O(E + VlogV),要求权值非负: ...
- Floyd-Warshall 全源最短路径算法
Floyd-Warshall 算法采用动态规划方案来解决在一个有向图 G = (V, E) 上每对顶点间的最短路径问题,即全源最短路径问题(All-Pairs Shortest Paths Probl ...
- Dijkstra 单源最短路径算法
Dijkstra 算法是一种用于计算带权有向图中单源最短路径(SSSP:Single-Source Shortest Path)的算法,由计算机科学家 Edsger Dijkstra 于 1956 年 ...
- Bellman-Ford 单源最短路径算法
Bellman-Ford 算法是一种用于计算带权有向图中单源最短路径(SSSP:Single-Source Shortest Path)的算法.该算法由 Richard Bellman 和 Leste ...
- 最短路径算法-Dijkstra
Dijkstra是解决单源最短路径的一般方法,属于一种贪婪算法. 所谓单源最短路径是指在一个赋权有向图中,从某一点出发,到另一点的最短路径. 以python代码为例,实现Dijkstra算法 1.数据 ...
- bzoj 4016: [FJOI2014]最短路径树问题
bzoj4016 最短路路径问题 Time Limit: 5 Sec Memory Limit: 512 MB Description 给一个包含n个点,m条边的无向连通图.从顶点1出发,往其余所有点 ...
- 51nod 1459 迷宫游戏 (最短路径—Dijkstra算法)
题目链接 中文题,迪杰斯特拉最短路径算法模板题. #include<stdio.h> #include<string.h> #define INF 0x3f3f3f3f ],v ...
- C++迪杰斯特拉算法求最短路径
一:算法历史 迪杰斯特拉算法是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法.是从一个顶点到其余各顶点的最短路径算法,解决的是有向图中最短路径问题.迪杰斯特拉算法主要特点是以 ...
- 求两点之间最短路径-Dijkstra算法
Dijkstra算法 1.定义概览 Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径.主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止.D ...
- 最短路径之Floyd算法
Floyd算法又称弗洛伊德算法,也叫做Floyd's algorithm,Roy–Warshall algorithm,Roy–Floyd algorithm, WFI algorithm. Floy ...
随机推荐
- 二、Nginx配置实例
Nginx配置实例 一.反向代理 实例一 1.实现效果 打开浏览器,在浏览器地址栏输入地址 www.123.com ,跳转到linux系统tomcat主页面中. 2.准备工作 在linux系统中安装t ...
- Orleans[NET Core 3.1] 学习笔记(四)( 2 )获取Grain的方式
简介 在这一节,我们将介绍如何在Silo和Client中获取Grain及调用Grain Grain获取方式 从Grain内部获取: //根据特定的Key值创建或获取指定的Grain IStudent ...
- Linux 防SSH暴力攻击
在下这几天发现我的VPS 总是莫名遭受到 江苏镇江那边的IP 登录请求攻击 ,跟踪了下路由,发现ip是从蒙古那边出去的,然后意识到可能是有扫描端口的.. 方法一: 现在的互联网非常不安全,很多人没事就 ...
- 消息队列 ActiveMQ的简单了解以及点对点与发布订阅的方法实现ActiveMQ
Apache ActiveMQ是Apache软件基金会所研发的开放源代码消息中间件: 由于ActiveMQ是一个纯Java程序,因此只需要操作系统支持Java虚拟机,ActiveMQ便可执行. Act ...
- excel 名次
RANK.AVG 函数 全部显示 全部隐藏 返回一个数字在数字列表中的排位:数字的排位是其大小与列表中其他值的比值:如果多个值具有相同的排位,则将返回平均排位. 语法 RANK.AVG(number, ...
- python数据类型(第二弹)
针对上一篇博文提出的若干种python数据类型,笔者将在本文和后续几篇博文中详细介绍. 本文着重介绍python数据类型中的整数型.浮点型.复数型.布尔型以及空值. 对于整数型.浮点型和复数型数据,它 ...
- PTA 符号配对 —— C++
请编写程序检查C语言源程序中下列符号是否配对:/*与 */.(与 ).[与].{与}. 输入格式: 输入为一个C语言源程序.当读到某一行中只有一个句点.和一个回车的时候,标志着输入结束.程序中需要检查 ...
- 通俗易懂的RESTful API实践详解(含代码)
来源:点击进入 点击上方链接,版面更好 一.什么是RESTful REST 是面向资源的,这个概念非常重要,而资源是通过 URI 进行暴露,URI 的设计只要负责把资源通过合理方式暴露出来就可以了,对 ...
- 安装JumpServer到CentOS(YUM)
运行环境 系统版本:CentOS Linux release 7.6.1810 (Core) 软件版本:JumpServer-1.4.8 硬件要求:最低2核4GB 官方文档:https://docs. ...
- Vue与原生APP的相互交互
现在好多APP都采用了Hybrid的开发模式,这种模式特别适合那些内容变动更新较大的APP,从而使得开发和日常维护过程变得集中式.更简短.更经济高效,不需要纯原生频繁发布.但有利肯定有弊咯,性能方面能 ...