#include<iostream>
#include<vector>
#include<cmath>
using namespace std;
typedef pair<int, int> PII;
const int N = 1e4 + 10, INF = 2147483647;
int n, m, s;
vector<PII> edges[N];
int dist[N];
bool st[N];
int main()
{
cin >> n >> m >> s;
for (int i = 1; i <= m; i++)
{
int u, v, w; cin >> u >> v >> w;
edges[u].push_back({ v, w });
}
// 初始化
for (int i = 0; i <= n; i++) dist[i] = INF;
dist[s] = 0;
// 开始 n - 1 次重复操作
for (int i = 1; i < n; i++)
{
// 1.找未确定最短路径最小的点
int a = 0;
for (int j = 1; j <= n; j++)
{
if (!st[j] && dist[j] < dist[a]) a = j;
}
// 2.标记成确定,进行松弛操作
st[a] = true;
for (auto& e : edges[a])
{
int b = e.first, c = e.second;
if (dist[b] > dist[a] + c) dist[b] = dist[a] + c;
}
}
for (int i = 1; i <= n; i++) cout << dist[i] << " ";
return 0;
}
经验总结:堆优化版的dijkstra算法,不再强制执行 n - 1 次重复操作,而是遍历完堆中所有数据,因为用堆优化带来的问题就决定了堆这个数据结构里面会存下所有可能的路径数据,只有把存在堆里面的数据全部遍历一边,才能确定最终dist数组中各个格子的值!
cpp复制代码
#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
using namespace std;
typedef pair<int, int> PII;
const int N = 1e5 + 10;
int n, m, s;
vector<PII> edges[N];
priority_queue<PII, vector<PII>, greater<PII>> heap; // 小根堆
int dist[N];
bool st[N];
void dijkstra()
{
// 初始化
memset(dist, 0x3f3f3f3f, sizeof dist);
dist[s] = 0;
heap.push({ 0, s });
// 堆里面最后会存下所有可能的路径,所以就不需要像常规版那样强制执行 n - 1 次,
// 这里是要把存在堆里面的所有数据都过一遍
while (heap.size())
{
// 1.找未确定最短路径且此时最短路径最小的点
auto x = heap.top(); heap.pop();
int a = x.second;
// 会有同一节点,不同最短路径的数据放入堆中,特判一下,
// 这里面的数据只会拿掉最短路径的那个数据,其他数据都是无效的
if (st[a]) continue;
// 标记成确定最短路径的点
st[a] = true;
// 松弛操作,比一比,看看有没有更合适的数据出现
for (auto& e : edges[a])
{
int b = e.first, c = e.second;
if (dist[a] + c < dist[b])
{
dist[b] = dist[a] + c;
heap.push({ dist[b], b });
}
}
}
for (int i = 1; i <= n; i++) cout << dist[i] << " ";
}
int main()
{
cin >> n >> m >> s;
for (int i = 1; i <= m; i++)
{
int u, v, w; cin >> u >> v >> w;
edges[u].push_back({ v, w });
}
dijkstra();
return 0;
}
Bellman-Ford 算法:
该算法适用于边权有负数的情况,而且还可以判断负环,存在负环的是没有最短路径的,下篇详讲。
该算法的本质是对所有边进行至多 n - 1 次的松弛操作,很暴力,但也很有用,具体操作流程如下:
1、首先是要有一个dist数组,和迪杰斯特拉算法有着一样的含义,即"disti表示从起点到 i 位置的最短路径";但是此处并没有st数组哦。
3.1、有的情况不需要跑满 n - 1 次,可以用bool flag 进行对每次单趟的判断,当有一次单趟没有进行松弛操作时,就可以break跳出 n - 1 次循环了。
OJ题来源:洛谷
OJ题名:【模板】单源最短路径(弱化版)
OJ题归属:图论基础【单源最短路】
解题算法:vector存图 + Bellman-Ford 算法
cpp复制代码
#include<iostream>
#include<vector>
#include<cmath>
using namespace std;
typedef pair<int, int> PII;
const int N = 1e4 + 10, INF = 2147483647;
int n, m, s;
vector<PII> edges[N];
int dist[N];
void bellman_ford()
{
// 初始化
for (int i = 0; i <= n; i++) dist[i] = INF;
dist[s] = 0;
// 优化一下:有些情况可能不需要跑满 n - 1 次
bool flag = false;
// 重复 n - 1 次松弛操作
for (int i = 1; i < n; i++)
{
// 单趟检测
flag = false;
// 枚举所有边
for (int u = 1; u <= n; u++) // 枚举各个点
{
if (dist[u] == INF) continue;
for (auto& e : edges[u]) // 枚举各个点的出边
{
int v = e.first, w = e.second;
if (dist[v] > dist[u] + w)
{
dist[v] = dist[u] + w;
flag = true;
}
}
}
if (flag == false) break;
}
for (int i = 1; i <= n; i++) cout << dist[i] << " ";
}
int main()
{
cin >> n >> m >> s;
for (int i = 1; i <= m; i++)
{
int u, v, w; cin >> u >> v >> w;
edges[u].push_back({ v, w });
}
bellman_ford();
return 0;
}
spfa算法这里新增了一个 bool stN 数组,sti表示 i 节点是否在队列里面,和之前的迪杰斯特拉算法是不一样的含义哦。
***这个算法最差情况是O(nm)的,出题人可以出这种图卡这个时间复杂度,标准版那个题就是。
OJ题来源:洛谷
OJ题名:【模板】单源最短路径(弱化版)
OJ题归属:图论基础【单源最短路】
解题算法:vector存图 + spfa 算法
cpp复制代码
#include<iostream>
#include<vector>
#include<queue>
#include<cmath>
using namespace std;
typedef pair<int, int> PII;
const int N = 1e4 + 10, INF = 2147483647;
int n, m, s;
vector<PII> edges[N];
int dist[N];
bool st[N];
void spfa()
{
// 初始化
for (int i = 0; i <= n; i++) dist[i] = INF;
queue<int> q;
q.push(s);
dist[s] = 0;
st[s] = true;
while (q.size())
{
auto a = q.front(); q.pop();
st[a] = false;
for (auto& e : edges[a])
{
int b = e.first, c = e.second;
if (dist[a] + c < dist[b])
{
dist[b] = dist[a] + c;
if (!st[b])
{
q.push(b);
st[b] = true;
}
}
}
}
for (int i = 1; i <= n; i++) cout << dist[i] << " ";
}
int main()
{
cin >> n >> m >> s;
for (int i = 1; i <= m; i++)
{
int u, v, w; cin >> u >> v >> w;
edges[u].push_back({ v, w });
}
spfa();
return 0;
}