朴素Dijkstra算法
#include <iostream>
#include <algorithm>
using namespace std;
// http://t.csdn.cn/Md07K
// 迪杰斯特拉算法
// 适用:单源最短路,所有边权都是正数,稠密图( m ~ n^2 )
const int N = 510;
int n, m;
// 边的存储用邻接矩阵(稠密图用邻接矩阵,稀疏图用邻接表)
int g[N][N];
int dist[N];
bool st[N]; // true表示该点已经确定最短路了
// 此算法只适合于求单源最短路,默认起点为1
void dijkstra()
{
// 初始化距离为 “无穷大”
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
// 一次循环会确认一个点的最短路径,n 个点即进行 n 次循环
for (int i = 0; i < n; i++)
{
// 找到未被确认最小路径的所有点中的最小路径值,该值可被确认为最小路径
int t = 0; // 0为无效点,且 0 为数组合法范围,可以作为比较点
for (int j = 1; j <= n; j++)
{
if (!st[j] && dist[t] > dist[j])
{
t = j;
}
}
st[t] = true;
// 用该点维护其他点
for (int j = 1; j <= n; j++)
{
dist[j] = min(dist[j], dist[t] + g[t][j]);
}
}
// 如果经过算法计算后 dist[i] 仍为 “无穷大” (0x3f3f3f3f),表明从 1 点无法到达 i 点
}
int main()
{
cin >> n >> m;
// 初始化邻接矩阵
memset(g, 0x3f, sizeof g);
while (m--)
{
int a, b, c;
cin >> a >> b >> c;
// 有重边的保留权重最小的边
g[a][b] = min(g[a][b], c);
}
dijkstra();
// 处理输出
for (int i = 1; i <= n; i++)
{
if (dist[i] != 0x3f3f3f3f)
{
cout << dist[i] << endl;
}
else
{
cout << -1 << endl;
}
}
return 0;
}
堆优化Dijkstra算法
#include <iostream>
#include <queue>
using namespace std;
/*
堆优化版dijkstra算法
适用:单源最短路,所有边权都是正数,稀疏图(n ~ m)
*/
// 堆中数据既要维护最短距离,也要存储对应的点,所以用 pair 存储
typedef pair<int, int> PII;
const int N = 100010;
int n, m;
// 边的存储方式为邻接表
int h[N], e[N], w[N], ne[N], idx;
// dist存储距离
int dist[N];
// st[i] 为 true 表示 i点 已经确认最短路了
bool st[N];
// 插入一条从 a 指向 b 权重为 c 的边
void insert(int a, int b, int c)
{
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx;
idx++;
}
void dijkstra()
{
// 初始化距离
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
// 建立小根堆维护距离
priority_queue<PII, vector<PII>, greater<PII>> hp;
// 将起点放进去
hp.push({ 0,1 });
// 堆不为空
while (hp.size())
{
PII t = hp.top();
hp.pop();
int ver = t.second;
int distance = t.first;
// 通过此步可以筛除重边
if (!st[ver])
{
st[ver] = true;
// 维护与该点相连的点
for (int i = h[ver]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > distance + w[i])
{
dist[j] = distance + w[i];
hp.push({ dist[j], j });
}
}
}
}
}
int main()
{
// 初始化邻接表表头
memset(h, -1, sizeof h);
cin >> n >> m;
while (m--)
{
int a, b, c;
cin >> a >> b >> c;
insert(a, b, c);
}
dijkstra();
for (int i = 1; i <= n; i++)
{
if (dist[i] == 0x3f3f3f3f)
{
cout << -1 << endl;
}
else
{
cout << dist[i] << endl;
}
}
return 0;
}
Bellman-Ford算法
#include <iostream>
using namespace std;
/*
贝尔曼-福特算法
适用:单源最短路,存在负权边,有边数限制
*/
const int N = 510;
const int M = 10010;
int n, m;
int dist[N];
struct Edge
{
// a指向b,权重为c
int a, b, c;
}edges[M];
void bellman_ford()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
// 如果第n次迭代仍然会松弛三角不等式,就说明存在一条长度是n+1的最短路径,由抽屉原理,路径中至少存在两个相同的点,说明图中存在负权回路。
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
int a = edges[i].a;
int b = edges[i].b;
int c = edges[i].c;
dist[b] = min(dist[b], dist[a] + c);
}
}
}
int main()
{
cin >> n >> m;
for (int i = 0; i < m; i++)
{
int a, b, c;
cin >> a >> b >> c;
edges[i] = { a,b,c };
}
bellman_ford();
for (int i = 1; i <= n; i++)
{
// 如果 dist[i] 大于一个比较大的数,则表明最短路不存在
// 因为即使某点的最短路不存在,在遍历边的时候该点的 0x3f3f3f3f 会被更新,不能用==0x3f3f3f3f来判断是否有最短路
if (dist[i] > 0x3f3f3f3f / 2)
{
cout << -1 << endl;
}
else
{
cout << dist[i] << endl;
}
}
return 0;
}
Bellman-Ford算法例题
#include <iostream>
using namespace std;
const int N = 510;
const int M = 10010;
/*
给定一个n个点m条边的有向图,图中可能存在重边和自环,边权可能为负数。
请你求出从1号点到n号点的最多经过k条边的最短距离,如果无法从1号点走到n号点,输出impossible.
注意:图中可能存在负权回路
输入格式
第一行包含三个整数n,m,k。
接下来m行,每行包含三个整数x,y,Z,表示点x和点y之间存在一条有向边,边长为Z。
输出格式
输出一个整数,表示从1号点到n号点的最多经过k条边的最短距离如果不存在满足条件的路径,则输出“impossible”。
*/
struct Edge
{
int a, b, c;
}edges[M];
int n, m, k;
int dist[N], backup[N];
void bellman_ford()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
for (int i = 0; i < k; i++)
{
// backup用来备份上一轮循环后的dist数据,并用此数据更新此轮循环
memcpy(backup, dist, sizeof dist);
for (int j = 0; j < m; j++)
{
int a = edges[j].a;
int b = edges[j].b;
int c = edges[j].c;
dist[b] = min(dist[b], backup[a] + c);
}
}
}
int main()
{
cin >> n >> m >> k;
for (int i = 0; i < m; i++)
{
int a, b, c;
cin >> a >> b >> c;
edges[i] = { a,b,c };
}
bellman_ford();
if (dist[n] > 0x3f3f3f3f / 2)
{
cout << "impossible" << endl;
}
else
{
cout << dist[n] << endl;
}
return 0;
}
SPFA算法
#include <iostream>
#include <queue>
using namespace std;
/*
SPFA算法
适用:单源最短路,存在负权边,无负环
*/
const int N = 100010;
int n, m;
int h[N], w[N], e[N], ne[N], idx; // 用邻接表存储边
int dist[N]; // 存储每个点到1号点的最短距离
bool st[N]; // 存储每个点是否在队列中
void insert(int a, int b, int c)
{
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx;
idx++;
}
void 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;
}
}
}
}
}
int main()
{
// 初始化邻接表表头
memset(h, -1, sizeof h);
cin >> n >> m;
for (int i = 0; i < m; i++)
{
int a, b, c;
cin >> a >> b >> c;
insert(a, b, c);
}
spfa();
for (int i = 1; i <= n; i++)
{
if (dist[i] == 0x3f3f3f3f)
{
cout << -1 << endl;
}
else
{
cout << dist[i] << endl;
}
}
return 0;
}
SPFA算法判断负环
#include <iostream>
#include <queue>
using namespace std;
/*
给定一个n个点m条边的有向图,图中可能存在重边和自环,边权可能为负数请你判断图中是否存在负权回路
输入格式
第一行包含整数n和m。
接下来m行每行包含三个整数x,y,z,表示点x和点y之间存在一条有向边,边长为Z。
输出格式
如果图中存在负权回路,则输出“Yes”,否则输出“No”
数据范围
1 < n < 2000,
1 < m < 10000,
图中涉及边长绝对值均不超过10000。
*/
int n, m;
const int N = 100010;
// 邻接表存储边
int h[N], e[N], w[N], ne[N], idx;
// 存储距离
int dist[N];
// 存储每个点到1点所经过的边数
int cnt[N];
// 存储每个点是否存在队列中
bool st[N];
void insert(int a, int b, int c)
{
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx;
idx++;
}
// 如果存在负环,则返回true,否则返回false。
bool spfa()
{
// 不需要初始化dist数组
// 原理:如果某条最短路径上有n个点(除了自己),那么加上自己之后一共有n+1个点,由抽屉原理一定有两个点相同,所以存在环。
queue<int> q;
// 题目询问的是存不存在负环,起点不一定是1点,所以要把所有点代进去求解
for (int i = 1; i <= n; i++)
{
q.push(i);
st[i] = 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];
// 维护边的个数
cnt[j] = cnt[t] + 1;
// 如果从1号点到x的最短路中包含至少n个点(不包括自己),则说明存在环
if (cnt[j] >= n)
{
return true;
}
if (!st[j])
{
q.push(j);
st[j] = true;
}
}
}
}
return false;
}
int main()
{
memset(h, -1, sizeof h);
cin >> n >> m;
for (int i = 0; i < m; i++)
{
int a, b, c;
cin >> a >> b >> c;
insert(a, b, c);
}
if (spfa())
{
cout << "Yes" << endl;
}
else
{
cout << "No" << endl;
}
return 0;
}
Floyd算法
#include <iostream>
using namespace std;
/*
弗洛伊德算法
适用:多源汇最短路,无负环
*/
const int N = 510;
int n, m;
int dist[N][N]; // dist[i][j] 表示从i到j的最短距离
void floyd()
{
// 三重循环
for (int k = 1; k <= n; k++)
{
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
}
}
}
}
int main()
{
cin >> n >> m;
// 初始化距离
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if (i == j)
{
dist[i][j] = 0;
}
else
{
dist[i][j] = 0x3f3f3f3f;
}
}
}
for (int i = 0; i < m; i++)
{
int a, b, c;
cin >> a >> b >> c;
dist[a][b] = min(dist[a][b], c);
}
floyd();
int i, j;
cin >> i >> j;
// 无最短路不一定==0x3f3f3f3f,因为在循环过程中,即使无最短路,0x3f3f3f3f也可能被更新(负权边)
if (dist[i][j] > 0x3f3f3f3f / 2)
{
cout << "impossible" << endl;
}
else
{
cout << dist[i][j] << endl;
}
return 0;
}