一、单源最短路径
1.朴素Dijkstra算法
#include <bits/stdc++.h>
using namespace std;
const int N = 1000;
//数组g为邻接矩阵用于存放权值, 数组dis[i]表示起点到节点i的距离, n代表点的个数, m代表边的个数
int g[N][N], dis[N], n, m;
bool v[N]; //数组v用于标记起点到当前节点的最短路径是否已经确定
void dijkstra() {
memset(dis, 0x3f, sizeof dis); //初始化起点到各个点的距离为 +∞
memset(v, 0, sizeof v); //初始化起点到各个点的最短距离为未确定状态
dis[1] = 0; //起点到起点的距离设为0
for (int i = 1; i < n; i++) {
int x = 0; //x表示起点到当前节点为最短距离的那个节点的下标
//找到未标记节点中dis最小的
for (int j = 1; j <= n; j++)
if (!v[j] && (x == 0 || dis[j] < dis[x])) //节点j未标记并且起点到节点j的距离比到节点x的距离短
x = j; //更新一下代表最短路径的节点下标
v[x] = 1; //起点到当前的节点x的距离必定最短了, 所以可以标记节点x了
for (int y = 1; y <= n; y++) //这次是遍历节点x的所有出边即x-->y
dis[y] = min(dis[y], dis[x] + g[x][y]); //由于起点到节点x的最短距离已经确定, 那么起点到节点y的距离根据左式更新一下就ok
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
memset(g, 0x3f, sizeof g);
for (int i = 1; i <= n; i++) g[i][i] = 0; //去除自环
for (int i = 1; i <= m; i++) {
int x, y, z;
cin >> x >> y >> z;
g[x][y] = min(g[x][y], z); //构建邻接矩阵
}
dijkstra();
//这部分内容根据题意自己写
return 0;
}
2.堆优化Dijkstra算法
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
typedef pair<int, int> ii;
typedef vector<ii> vii;
const int N = 10000;
int to[N], v[N], ne[N], h[N];
int dis[N];
int n, m, idx;
bool vv[N]; //上面有个v了又懒得写vis所以这里用vv代替(懒癌晚期)
//链式前向星加边
void add(int x, int y, int z) {
to[++idx] = y, v[idx] = z;
ne[idx] = h[x], h[x] = idx;
}
void dijkstra() {
memset(dis, 0x3f, sizeof dis); //初始化起点到各个点的距离为 +∞
memset(vv, 0, sizeof vv); //初始化起点到各个点的最短距离为未确定状态
dis[1] = 0; //起点到起点的距离设为0
//初始化小根堆, 第一个元素为距离, 第二个元素为节点编号,队内升序排列确保队头元素最小
priority_queue<ii, vii, greater<ii>> q;
q.push({0, 1}); //起点入队
while (q.size()) {
int x = q.top().second; q.pop(); //取出当前队头的下标, 记得还要弹出队头
if(vv[x]) continue; //对于已经确定的最短路径则跳过
vv[x] = 1; //将节点x标记一下
//链式前向星遍历x的出边, 即x-->y
for(int i = h[x]; ~i; i = ne[i]){
int y = to[i], z = v[i];
if(dis[y] > dis[x] + z){
dis[y] = dis[x] + z;
q.push({dis[y],y});
}
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
idx = 0;
memset(h, -1, sizeof h); //初始化,h为-1则代表指向空
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int x, y, z;
cin >> x >> y >> z;
add(x, y, z);
}
dijkstra();
//这个部分根据题意自己写
return 0;
}
3.Bellman-Ford算法
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
const int N = 1000, M = 1000;
int n, m, idx;
int dis[N];
//直接用结构体存图
struct edge {
int x, y, z;
} e[M];
void Bellman_Ford() {
memset(dis, 0x3f, sizeof dis);
dis[1] = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
int x = e[j].x, y = e[j].y, z = e[j].z;
if (dis[y] > dis[x] + z) //利用三角不等式进行松弛操作
dis[y] = dis[x] + z;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
idx = 0;
for (int i = 0; i < m; i++) {
int x, y, z;
cin >> x >> y >> z;
//双向连边
e[++idx].x = x, e[idx].y = y, e[idx].z = z;
e[++idx].x = y, e[idx].y = x, e[idx].z = z;
}
Bellman_Ford();
//一般这一块写判负环操作, 存在负环则输出-1, 否则输出起点到节点n的最短路径
if (dis[n] > INF / 2) cout << -1 << endl;
else cout << dis[n] << endl;
return 0;
}
4.SPFA算法
4.1 常规SPFA
#include <bits/stdc++.h>
using namespace std;
const int N = 1000, M = 10000;
int to[N], v[N], ne[N], h[N];
int dis[N];
int n, m, idx;
bool vv[N]; //数组vv用于标识当前节点是否在队列中, 避免重复处理
queue<int> q;
//链式前向星加边
void add(int x, int y, int z) {
to[++idx] = y, v[idx] = z;
ne[idx] = h[x], h[x] = idx;
}
void SPFA() {
memset(dis, 0x3f, sizeof dis);
memset(vv, 0, sizeof vv);
dis[1] = 0;
q.push(1); vv[1] = 1;
while (q.size()) {
int x = q.front(); q.pop();
vv[x] = 0; //出队后对应的节点标记为不在队列中
//链式前向星遍历x的出边, 即x-->y
for (int i = h[x]; ~i; i = ne[i]) {
int y = to[i], z = v[i];
if (dis[y] > dis[x] + z) {
dis[y] = dis[x] + z;
if (!vv[y]) q.push(y), vv[y] = 1; //如果y不在队列中则加入
}
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
idx = 0;
memset(h, -1, sizeof h); //使用链式前向星一定要记得初始化
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int x, y, z;
cin >> x >> y >> z;
add(x, y, z);
}
SPFA();
//这个部分根据题目自己写
return 0;
}
4.2 SPFA判负环
#include <bits/stdc++.h>
using namespace std;
const int N = 1000, M = 10000;
int to[N], v[N], ne[N], h[N];
int dis[N], cnt[N]; //数组cnt用于存储起点到当前节点一共有多少条边
int n, m, idx;
bool vv[N]; //数组vv用于标识当前节点是否在队列中, 避免重复处理
queue<int> q;
//链式前向星加边
void add(int x, int y, int z) {
to[++idx] = y, v[idx] = z;
ne[idx] = h[x], h[x] = idx;
}
bool SPFA() {
//这里不能只把起点加入队列了,有可能从起点到不了存在负环的地方, 所以要把所有的点都加入队列
for(int i = 1;i <= n; i++){
q.push(i);
vv[i] = 1;
}
while (q.size()) {
int x = q.front(); q.pop();
vv[x] = 0; //出队后对应的节点标记为不在队列中
//链式前向星遍历x的出边, 即x-->y
for (int i = h[x]; ~i; i = ne[i]) {
int y = to[i], z = v[i];
if (dis[y] > dis[x] + z) {
dis[y] = dis[x] + z;
cnt[y] = cnt[x] + 1; //更新一下边数
//根据抽屉原理, 如果起点到节点y一共经过了n条边则路径上必然有n+1个点, 说明存在负环
if(cnt[y] >= n) return true;
if (!vv[y]) q.push(y), vv[y] = 1; //如果y不在队列中则加入
}
}
}
return false;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
idx = 0;
memset(h, -1, sizeof h); //使用链式前向星一定要记得初始化
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int x, y, z;
cin >> x >> y >> z;
add(x, y, z);
}
//判负环
if(SPFA()) cout<<"YES"<<endl;
else cout<<"NO"<<endl;
return 0;
}
二、All-Pairs最短路径问题
Floyd-Warshall算法
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
const int N = 1000;
int n, m; //n个点, m条边
int g[N][N]; //数组g为邻接矩阵用于存储边权
void floyd() {
for (int k = 1; k <= n; k++) //k是决策阶段, 所以一定要写在最外层, i和j为附加状态写在内层
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
g[i][j] = min(g[i][j], g[i][k] + g[k][j]);
//上式本质上是动态规划, 原本式子为g[k][i][j] = min(g[k-1][i][j], g[k-1][i][k] + g[k-1][k][j]);
//但是k-1这个决策阶段对k是无影响的, 所以忽略决策阶段降为2维, 一个是从i直接到j, 另一个是从i到k再到j, 取两者最小值即得状态方程
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
memset(g, 0x3f, sizeof g);
for (int i = 1; i <= n; i++) g[i][i] = 0; //去除自环
for (int i = 1; i <= m; i++) {
int x, y, z;
g[x][y] = min(g[x][y], z); //构建邻接矩阵
}
floyd();
//这个部分根据题意自己填
return 0;
}