g[][]
、记录到 1 1 1 节点距离的dist[]
、还有标记节点是否已经求得最短路的st[]
dist[1] = 0
,意思就是 1 1 1 节点到 1 1 1 节点的最短距离是 0 0 0重复 n n n 轮,每轮遍历所有节点找最小值,然后遍历所有边,所以是 O ( n ⋅ ( n + n ) ) O(n · (n + n)) O(n⋅(n+n)),也就是 O ( n 2 ) O(n^2) O(n2)
题目链接:https://www.acwing.com/activity/content/problem/content/918/
#include // 用于输入和输出
#include // 用于处理字符串
#include // 用于算法操作
using namespace std;
const int N = 520; // 定义常量N
int n, m; // 定义整型变量n和m
int g[N][N]; // 定义二维数组g
int dist[N]; // 定义一维数组dist
bool st[N]; // 定义布尔数组st
int dijkstra(){ // 定义dijkstra函数
memset(dist, 0x3f, sizeof dist); // 初始化dist数组
dist[1] = 0; // 设置dist数组的第一个元素为0
for(int i = 1; i <= n; ++i){ // 遍历n个节点
int t = -1;
for(int j = 1; j <= n; ++j) // 寻找未被访问的距离最小的节点
if(!st[j] && (t == -1 || dist[j] < dist[t]))
t = j;
st[t] = true; // 标记节点t已被访问
for(int j = 1; j <= n; ++j) // 更新所有节点到源点的距离
dist[j] = min(dist[j], dist[t] + g[t][j]);
}
// 如果dist[n]的值未改变,返回-1,否则返回dist[n]
return dist[n] == 0x3f3f3f3f ? -1 : dist[n];
}
int main() // 主函数
{
cin >> n >> m; // 输入n和m
memset(g, 0x3f, sizeof g); // 初始化g数组
int a, b, c;
while (m -- ){ // 输入m条边
scanf("%d%d%d", &a, &b, &c);
g[a][b] = min(g[a][b], c); // 更新边的权值
}
int t = dijkstra(); // 调用dijkstra函数
cout << t << endl; // 输出结果
}
我们可以发现,朴素 D j i k s t r a Djikstra Djikstra 一共分为三步:
其中最后两步:找出最小和更新距离可以用我们学过的一个数据结构 - 堆 来优化
题目链接:https://www.acwing.com/activity/content/problem/content/919/
#include
#include
#include
#include
using namespace std;
typedef pair<int, int> pii;
const int N = 1e6 + 10;
int h[N], e[N], ne[N], w[N], idx;
int dist[N];
bool st[N];
int n, m;
int a, b, c;
void add(int a, int b, int c){
e[idx] = b;
ne[idx] = h[a];
w[idx] = c;
h[a] = idx++;
}
int dijkstra(){
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
priority_queue<pii, vector<pii>, greater<pii>> heap;
heap.push({0, 1});
while(heap.size()){
auto t = heap.top();
heap.pop();
int ver = t.second, distance = t.first;
if(st[ver]) continue;
st[ver] = true;
for(int i = h[ver]; i != -1; i = ne[i]){
int j = e[i];
if(dist[j] > dist[ver] + w[i]){
dist[j] = dist[ver] + w[i];
heap.push({dist[j], j});
}
}
}
return dist[n] == 0x3f3f3f3f ? -1 : dist[n];
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
while (m -- ){
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
cout << dijkstra() << endl;
}
D i j k s t r a Dijkstra Dijkstra 算法没办法处理负权图,为什么呢?
总结来说,就是 D i j k s t r a Dijkstra Dijkstra 算法采用了贪心思想,所以出错,原因如下:
dist[]
值可以被更新,但是要想再用正确的最短路的值更新其他节点就做不到了(因为被打上标记了),所以最后的答案是可能是错的。它们一刻也没有为 D i j k s t r a Dijkstra Dijkstra 算法而哀悼,立刻赶来战场的是 — B e l l m a n − f o r d Bellman-ford Bellman−ford 算法和 S P F A SPFA SPFA 算法!!!
两遍循环,一重循环循环 n n n 次,每次循环都遍历每条边,把能更新的都更新一遍,那么就是 O ( n m ) O(nm) O(nm)
-INF
。题目链接:https://www.acwing.com/activity/content/problem/content/922/。
#include // 引入输入输出流库
#include // 引入字符串处理库
#include // 引入算法库
#define INF 0x3f3f3f3f // 定义无穷大的值
using namespace std;
const int N = 510, M = 10010; // 定义常量N和M
struct{ // 定义一个结构体,用于存储边的信息
int a, b, c;
}edges[M];
int n, m, k; // 定义变量n,m,k
int a, b, c; // 定义变量a,b,c
int dist[N], backup[N]; // 定义数组dist和backup
void bellman_ford(){ // 定义Bellman-Ford算法函数
memset(dist, 0x3f, sizeof dist); // 初始化dist数组
dist[1] = 0; // 将dist数组的第一个元素设为0
for(int i = 0; i < k; ++i){ // 对每个顶点进行遍历
memcpy(backup, dist, sizeof dist); // 将dist数组的值复制到backup数组
for(int j = 0; j < m; ++j){ // 对每条边进行遍历
auto e = edges[j]; // 获取当前边的信息
dist[e.b] = min(dist[e.b], backup[e.a] == INF ? INF : backup[e.a] + e.c); // 更新dist数组的值
}
}
}
int main() // 主函数
{
cin >> n >> m >> k; // 输入n,m,k的值
for(int i = 0; i < m; ++i){ // 对每条边进行遍历
scanf("%d%d%d", &a, &b, &c); // 输入a,b,c的值
edges[i] = {a, b, c}; // 将a,b,c的值存储到edges数组
}
bellman_ford(); // 调用Bellman-Ford算法函数
if(dist[n] == INF) puts("impossible"); // 如果dist数组的最后一个元素等于无穷大,输出"impossible"
else printf("%d\n", dist[n]); // 否则,输出dist数组的最后一个元素的值
}
backup[]
数组:
dist[e.b] = min(dist[e.b], backup[e.a] == INF ? INF : backup[e.a] + e.c);
min()
函数比较更新前后的值的时候就会把 B B B 的最短路更新为 I N F − s INF - s INF−s ,但是其实 B B B 依然没有最短路,对于终点情况也是一样。min()
函数,不进行判断的话,需要在最后判断是否有最短路的时候把判断改为if (dist[n] > 0x3f3f3f3f / 2) puts("impossible");
只要大于一个不可能的数就判断为没有最短路。有两个应用
将 B e l l m a n − f o r d Bellman-ford Bellman−ford 算法优化一下,每次不将所有边都遍历一遍,而是将更新后的节点入队,每次从队列里面拿节点进行更新。
题目链接:https://www.acwing.com/activity/content/problem/content/920/。
#include // 引入输入输出流库
#include // 引入字符串处理库
#include // 引入算法库
#include // 引入队列库
#define INF 0x3f3f3f3f // 定义无穷大的值
using namespace std;
const int N = 1e5 + 10; // 定义常量N
int h[N], e[N], ne[N], w[N], idx; // 定义数组h,e,ne,w和变量idx
int n, m; // 定义变量n,m
int a, b, c; // 定义变量a,b,c
bool st[N]; // 定义布尔数组st
int dist[N]; // 定义数组dist
void add(int a, int b, int c){ // 定义添加边的函数
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
int spfa(){ // 定义SPFA算法函数
memset(dist, 0x3f, sizeof dist); // 初始化dist数组
dist[1] = 0; // 将dist数组的第一个元素设为0
queue<int> q; // 定义一个队列q
q.push(1); // 将1压入队列
st[1] = true; // 将st数组的第一个元素设为true
while(q.size()){ // 当队列不为空时
auto t = q.front(); // 获取队列的第一个元素
q.pop(); // 将队列的第一个元素弹出
st[t] = false; // 将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个元素加上边的权值
dist[j] = dist[t] + w[i]; // 更新dist数组的第j个元素的值
if(!st[j]){ // 如果st数组的第j个元素为false
q.push(j); // 将j压入队列
st[j] = true; // 将st数组的第j个元素设为true
}
}
}
}
return dist[n]; // 返回dist数组的最后一个元素的值
}
int main() // 主函数
{
memset(h, -1, sizeof h); // 初始化h数组
cin >> n >> m; // 输入n,m的值
while (m -- ){ // 对每条边进行遍历
scanf("%d%d%d", &a, &b, &c); // 输入a,b,c的值
add(a, b, c); // 调用add函数,添加边
}
int ans = spfa(); // 调用spfa函数,获取最短路径的长度
if(ans == INF) puts("impossible"); // 如果ans等于无穷大,输出"impossible"
else printf("%d\n", ans); // 否则,输出ans的值
}
st[]
数组:用来标记队列中的节点,已经入队了就没必要再次入队了,防止无限入队。if
判断是否更新而不是直接用min()
函数:防止无限入队。维护一个cnt[]
数组,每当某节点被更新一次就在源点对应的记录数组cnt[]
上+1
,当某个记录数突破了节点数时,肯定存在负环,证明详情见 B e l l m a n − f o r d Bellman-ford Bellman−ford 算法y总原话之抽屉原理
题目链接:https://www.acwing.com/activity/content/problem/content/921/。
#include
#include
#include
#include
using namespace std;
const int N = 2010, M = 10010;
int n, m;
int a, b, c;
int h[N], e[M], ne[M], w[M], idx;
int dist[N], cnt[N];
bool st[N];
void add(int a, int b, int c){
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
bool spfa(){
//memset(dist, 0x3f, sizeof dist); 加不加都行
//dist[1] = 0;
queue<int> q;
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;
if(!st[j]){
q.push(j);
st[j] = true;
}
if(cnt[j] >= n) return true;
}
}
}
return false;
}
int main()
{
memset(h, -1, sizeof h);
cin >> n >> m;
while (m -- ){
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
if(spfa()) puts("Yes");
else puts("No");
}
dist[]
不用初始化:
dist[]
永远不会更新,直到遇见了负数才会更新,如果有负环那就会一直更新直到爆 n n n 返回false
采用了动态规划思想,具体怎样我也不知道 >_<,没学过呜呜呜。
总之很短很暴力。
别问,问就是背过
菜就多练,学不会就背过。
暴力是暴力, F l o y d Floyd Floyd 是 F l o y d Floyd Floyd 。
你要是一直拿 F l o y d Floyd Floyd 当暴力。
你咋不去自己写一个?
三重循环所以是—— O ( n 3 ) O(n^3) O(n3)
太美丽了家人们。
题目链接:https://www.acwing.com/activity/content/problem/content/923/。
#include
#include
#include
#define INF 0x3f3f3f3f
using namespace std;
int n, m, k;
const int N = 210, M = 20010;
int d[N][N];
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] == INF || d[k][j] == INF) ? INF : d[i][k] + d[k][j]);
// y总写的d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
int main()
{
cin >> n >> m >> k;
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] = INF;
while (m -- ){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
d[a][b] = min(d[a][b], c);
}
floyd();
while(k--){
int a, b;
scanf("%d%d", &a, &b);
if(d[a][b] == INF) puts("impossible");
else printf("%d\n", d[a][b]);
}
}
INF - k
的情况出现,所以加了个判断。WA
,但是一起判断了就AC
了,所以我就这么写了,原理我也不懂,嘿嘿。写了一天总算写完了,真的很复杂啊 %%%%%%%%%%%%%%