单源最短路径
单源最短路:求一个点到其他所有点的最短距离。
所有边权都是正数
所有边权都是正数:所有边的权重都是正数。
朴素Dijkstra算法
朴素Dijkstra算法的时间复杂度为O(n²)(n表示点数),与边没有关系,所以我们多用朴素Dijkstra算法来解决稠密图的问题。
实现过程
1.初始化距离。
- dist[1] = 0;dist[i] = +oo;
2.迭代循环n次,确定到每个点的最短距离。
- 先假设一个数组st[]用来储存当前已确定最短距离的点,t用来存不在st[]中的当前最短距离的点。
- 再循环得到最短距离后,将t存入st[]中。
- 最后用t来更新其他点的距离。
图示
代码模板
int dijkstra()
{
memset(dist, 0x3f,sizeof dist);//初始化距离
dist[1] = 0;//dist[]表示各个点到起点的距离
for(int i = 0; i < n; i ++)//迭代循环n次
{
int t = -1;
for(int j = 1; j <= n; j ++) //寻找最短距离 ,st[]判断该点是否已经是最短路径
{
if(!st[j] && (t = -1 || dist[t] > dist[j]))// dist[t] > dist[j]用来判断当前的路径是否是最短的
t = j;
}
st[t] = true;//该点已经确定最短距离
for(int j = 1; j <= n; j ++)//用t来更新其他点(这里的其他点是指从t点出发,能直达的点)到起点的距离
{
dist[j] = min(dist[j],dist[t] + g[t][j]);//g[t][j]表示t点到j点的距离
}
}
if(dist[n] == 0x3f3f3f3f)//输出到n点的最短距离 (当然输出其他点也可以)
return -1;
else
return dist[n];
}
堆优化版的Dijkstra算法
堆优化版的Dijkstra算法的时间复杂度为O(mlog(n))(n表示点数,m表示边数),多用来解决稀疏图的问题。
实现操作
在朴素Dijkstra算法的基础上,用优先队列优化循环过程。
代码模板
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
typedef pair<int,int> PII;//用邻接表储存所有边
const int N = ... ;
...
int h[N], e[N], ne[N], w[N], idx;//h[]用来储存点对应的下标,w[]用来储存权重
int dist[N];//dist[]用来储存点到起点的距离
bool st[N]; //判断该点是否已经确定最短距离
void add(int a, int b, int c)//邻接表添加结点
{
e[idx] = b; w[idx] = c; ne[idx] = h[a]; h[a] = idx++;
}
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);//初始化不变
dist[1] = 0;
priority_queue<PII, vector<PII>, greater<PII>> heap;//优先队列初始化是大根堆,所以我们要用greater函数,让它变成小根堆
//<类型,<储存方式>,<比较函数> >
heap.push({0,1});//first储存距离,second储存编号
//pair 定义了自己的排序规则:先比较第一维,相等了再比较第二维因此储存时需要按照{dist[i],i},而不是{i,dist[i]}的方式组合
while(heap.size())
{
auto t = heap.top(); //取出队首元素并且弹出队列
heap.pop();
int a = t.second, b = t.first;//a表示从1~这个点,b表示路径长度
if(st[a] == true) continue;//如果是确定的最短路则直接进行下一循环
st[a] = true;
for(int i = h[a]; i != -1; i = ne[i])//h[]表示当前结点的下标
{
int j = e[i];//此时i代表当前节点的开始位置,j代表节点的结束位置,w[i]指的是路径距离
if(dist[j] > dist[a] + w[i])//更新dist[]数组
{
dist[j] = dist[a] + w[i];
heap.push({dist[j], j});
}
}
}
if(dist[n] == 0x3f3f3f3f)
return -1;
else
return dist[n];
}
int main()
{
...
...
memset(h, -1, sizeof h);//h[]初始化都为-1
int a, b, c;
cin >> a >> b >> c;
add();
...
...
... dijkstra();
return 0;
}
存在负权边
存在负权边:某些边的权重存在负值。
Bellman-Ford算法
Bellman-Ford算法时间复杂度为O(nm)
int n, m; // n表示点数,m表示边数
int dist[N]; // dist[x]存储1到x的最短路距离
struct Edge // 边,a表示出点,b表示入点,w表示边的权重
{
int a, b, w;
}edges[M];
// 求1到n的最短路距离,如果无法从1走到n,则返回-1。
int bellman_ford()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
// 如果第n次迭代仍然会松弛三角不等式,就说明存在一条长度是n+1的最短路径,由抽屉原理,路径中至少存在两个相同的点,说明图中存在负权回路。
for (int i = 0; i < n; i ++ ) //进行n次松弛(迭代)
{
for (int j = 0; j < m; j ++ )
{
int a = edges[j].a, b = edges[j].b, w = edges[j].w;
dist[b] = min(dist[b],dist[a] + w);
}
}
if (dist[n] > 0x3f3f3f3f / 2) return -1;
return dist[n];
}
SPFA算法
SPFA算法是Bellman-Ford算法的优化,其的时间复杂度一般为O(m),最坏的情况的话为O(nm)。
AcWing 851. spfa求最短路
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
typedef pair<int,int> PII;
const int N = 2e5;
int n,m; // 点的数量
int h[N], w[N], e[N], ne[N], idx; // 邻接表存储所有边
int dist[N]; // 存储所有点到1号点的距离
bool st[N]; // 存储每个点的最短距离是否已确定
void add(int a, int b, int c) //邻接表添加节点
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
// 求1号点到n号点的最短路距离,如果从1号点无法走到n号点则返回-1
int spfa()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
queue<int> q;
q.push(1);
st[1] = true;
while(q.size())
{
int t = q.front();
q.pop();
st[t] = false;
for(int i = h[t];i != -1; i = ne[i])
{
int j = e[i];
if(dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
if(!st[j]) // 如果队列中已存在j,则不需要将j重复插入
{
q.push(j);
st[j] = true;
}
}
}
}
if(dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
int main()
{
cin >> n >> m;
memset(h,-1,sizeof h);
while(m--)
{
int a,b,c;
cin >> a >> b >> c;
add(a,b,c);
}
cout << spfa();
return 0;
}
多源汇最短路
多源汇最短路:起点和终点都不确定,求从某一起点到某一终点的最短路距离。
Floyd算法
Floyd算法的时间复杂度为O(n³)。
初始化:
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
d[i][j] = i == j ? 0 : INF;
// 算法结束后,d[a][b]表示a到b的最短距离
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]);
}