最短路算法框架
最短路问题的求解主流有五种算法,分别适用不同的情况
1.朴素Dijkstra算法
2.堆优化版Dijkstra算法
3.Bellman-Ford算法
4.SPFA算法
5.Floyd算法
单源最短路: 求一个点到其他点的最短路
多源最短路: 求任意两个点的最短路
稠密图: m 和 n^2 一个级别
稀疏图: m 和 n 一个级别
稠密图用邻接矩阵存,稀疏图用邻接表存
朴素Dijkstra算法
算法性质
适用于单源最短路径问题。
适用于稠密图,使用邻接矩阵存储图。
时间复杂度为O(n^2),对于稀疏图可能较慢。
不能处理负权边,但可以处理非负权图。
使用贪心思想,每次选择当前未访问的距离起点最近的点,逐步构建最短路径。
实现步骤
初始化距离数组,将起点到自身的距离设置为0,其他点到起点的距离设置为无穷大。
循环n次,每次选择当前未访问的点中距离起点最近的点,并标记为已访问。
通过新加入的点更新其他点的最短距离。
模板代码
#include<bits/stdc++.h>
using namespace std;
struct point {
int to, cost;
};
vector<point> g[100010]; // 邻接矩阵 存图
int dis[100010]; // dis[i] 表示从起点s到终点i的最短长度
bool vis[100010]; // 记录点是否访问过
int n, m, s; // 点数,边数,起点
void dijkstra()
{
dis[s] = 0; // 起点到自己的距离为0
for (int i = 1; i <= n; i++)
{
int t = -1;
for (int j = 1; j <= n; j++)
{
if (!vis[j] && (t == -1 || dis[j] < dis[t])) {
t = j;
}
}
vis[t] = true; // 将最小距离的点标记为已访问
for (int j = 1; j <= n; j++) {
dis[j] = min(dis[j], dis[t] + g[t][j].cost); // 通过t更新其他点的距离
}
}
}
int main()
{
cin >> n >> m >> s; // 读取点数,边数,起点
memset(dis, 0x3f, sizeof(dis)); // 初始化距离数组为无穷大
while (m--)
{
int u, v, d;
cin >> u >> v >> d; // 读取边的起点,终点和权值
g[u].push_back({ v,d }); // 添加边的信息到邻接矩阵
}
dijkstra(); // 调用朴素Dijkstra算法求解最短路径
for (int i = 1; i <= n; i++) {
cout << dis[i] << " "; // 输出从起点到每个点的最短距离
}
return 0;
}
堆优化版Dijkstra算法
算法性质
适用于单源最短路径问题。
适用于稀疏图,使用邻接表存储图。
时间复杂度为O(mlogn),在边数相对点数较少时较快。
不能处理负权边,但可以处理非负权图。
通过使用最小堆优化朴素 Dijkstra 算法,提高了贪心地寻找最小距离点的效率,减少了不必要的比较。
实现步骤
初始化距离数组,将起点到自身的距离设置为0,其他点到起点的距离设置为无穷大。
建立一个最小堆,将起点入队,并不断从堆中取出距离起点最近的点。
通过新加入的点更新其他点的最短距离。
模板代码
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> node;
struct point{
int to,cost;
};
vector<point> g[100010];//邻接矩阵 存图
int dis[100010];//dis[i] 从起点s 到终点i 的最短长度
bool vis[100010];//是否访问
int n,m,s;
void dijkstra()
{
priority_queue<node,vector<node>,greater<node> > q;
dis[s] = 0;
q.push({0,s});
while(!q.empty())
{
node tmp = q.top();
q.pop();
int x = tmp.second;
int d = tmp.first;
if(vis[x]){
continue;
}else{
vis[x] = 1;
}
for(int i=0;i<g[x].size();i++){
int y = g[x][i].to;
int newDis = d + g[x][i].cost;
if(dis[y] > newDis)
{
dis[y] = newDis;
if(!vis[y])
{
q.push({newDis, y} );
}
}
}
}
}
int main()
{
cin>>n>>m>>s;
memset(dis,0x3f3f3f3f,sizeof dis);
for(int i=0;i<m;i++)
{
int u, v, d;
cin>>u>>v>>d;
g[u].push_back({v,d});
}
dijkstra();
for(int i=1;i<=n;i++){
cout<<dis[i]<<" ";
}
return 0;
}
Bellman-Ford算法
算法性质
适用于单源最短路问题,可处理带负权边的图。
时间复杂度为O(nm),效率较低。
可检测负权回路,用于判断图是否存在负权回路。
采用动态规划思想,通过不断更新点之间的最短距离,逐步求解出整个图的最短路径。
实现步骤
初始化距离数组,将起点到自身的距离设置为0,其他点到起点的距离设置为无穷大。
循环n-1次,对所有边进行松弛操作,即通过已知的边更新到达其他点的最短距离。
循环n次,检查是否存在负权回路
模板代码
#include <bits/stdc++.h>
#define INF 2147483647
using namespace std;
struct node{
long long from,to,cost;
}es[1000010<<1];
long long d[1000010<<1];
long long n,m,s;//点,边,起
void bellman_ford_getShortest_Path(long long s){
//初始化
for(int i=1;i<=n;i++)d[i] = INF;
d[s] = 0;
//开始更新
while(true){
bool update = false;
for(int i=1;i<=m;i++){
node e = es[i];
//如果发现 d[e.from] 的距离更新了
//并且 从 e.from 到 e.to 的距离更短
//则更新 d[e.to]
if(d[e.from] != INF && d[e.to] > d[e.from] + e.cost){
d[e.to] = d[e.from]+e.cost;
update = true;
}
}
if(!update)break;
}
}
int main() {
//读图
cin>>n>>m>>s;
for(int i=1;i<=m;i++){
long long u,v,w;
cin>>u>>v>>w;
es[i] = {u,v,w};
}
//开始计算最短路
bellman_ford_getShortest_Path(s);
//输出从起点开始到第0~n个点的最短距离
for(int i=1;i<=n;i++){
cout<<d[i]<<" ";
}
}
SPFA算法
算法性质
适用于单源最短路问题,可处理带负权边的图
队列优化版的Bellman-Ford算法,常常比Bellman-Ford快,但可能在存在负权回路的情况下陷入死循环
平均时间复杂度取决于实际图的特点,一般情况下优于Bellman-Ford
时间复杂度 最坏 O(nm) 容易被卡!!!它已经死辣!
SPFA算法通过队列只对当前最近的点进行松弛操作,减少了不必要的计算。
实现步骤
初始化距离数组,将起点到自身的距离设置为0,其他点到起点的距离设置为无穷大。
建立一个队列,将起点入队,并不断从队列中取出点。
通过新加入的点更新其他点的最短距离。
模板代码
#include <bits/stdc++.h>
using namespace std;
int n,m,s;
struct point{
int to,cost;
};
int vis[100010];
int dis[100010];
vector<point> g[100010];
void spfa(){
queue<int> q;
memset(dis, 0x3f, sizeof dis);
dis[s] = 0;
vis[s] = true;
q.push(s);
while(!q.empty()){
int u = q.front();
q.pop();
vis[u] = false;
for(int i=0;i<g[u].size();i++){
if(dis[g[u][i].to]>dis[u]+g[u][i].cost){
dis[g[u][i].to] = dis[u]+g[u][i].cost;
if(!vis[g[u][i].to]){
q.push(g[u][i].to);
vis[g[u][i].to] = true;
}
}
}
}
}
int main(){
//读图
cin>>n>>m>>s;
for(int i=1;i<=m;i++){
int u,v,w;
cin>>u>>v>>w;
g[u].push_back({v,w});
}
spfa();
for(int i=1;i<=n;i++){
cout<<dis[i]<<" ";
}
return 0;
}
Floyd算法
算法性质
适用于多源最短路问题,求任意两个点之间的最短路径。
时间复杂度为O(n^3),适合处理点数较少的图。
适合解决稠密图中的最短路径问题。
可处理带负权边的图,但不能存在负权回路。
使用动态规划思想,通过不断利用中转点更新任意两点之间的最短距离,逐步求解出整个图的最短路径。
实现步骤
初始化距离数组,将点之间的距离初始化为边的权值,自己到自己的距离为0。
通过中转点遍历更新任意两点之间的最短距离。
模板代码
#include <bits/stdc++.h>
using namespace std;
int n, m;
int d[210][210];
void 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]);
}
}
}
}
int main() {
//Read In
cin >> n >> m;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
if(i == j){
d[i][j] = 0;
}else{
d[i][j] = 0x3f3f3f3f;
}
}
}
for(int i=1;i<=m;i++){
int x,y,z;
cin >> x >> y >> z;
d[x][y] = d[y][x] = min(d[x][y], z);
}
floyd();
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
cout<<d[i][j]<<" ";
}
cout<<endl;
}
return 0;
}