算法学习12:最短路问题
文章目录
前言
😊暂时只用记住:dijkstra和spea算法😊
提示:以下是本篇文章正文内容:
一、朴素Dijkstra:(稠密图:邻接矩阵)(邻接矩阵)
给定n个点,m条边的有向图,所有边均为正值。
请你求出1号点到n号点的最短距离,如果无法从1号点走到n好点,输出-1.
//1 < n <= 500, 1 < m <= 10^5 (稠密图:邻接矩阵)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 500 + 10;
// //1 < n <= 500, 1 < m <= 10^5 (稠密图:邻接矩阵)
int n, m;
int g[N][N];// g[i][j]:i到j的边长
int dist[N];// 距离
bool st[N];// 确定最短路
// 求出1号点到n号点的最短距离
int dijkstra()
{
memset(dist, 0x3f3f3f, sizeof dist);
dist[1] = 0;
for(int i = 0; i < n; i ++)
{
int t = -1;
// 当前没有确定最短路的点中,距离最小的那一个。
for(int j = 1; j <= n; j ++)// 编号:1 ~ n
if(!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;// 如果当前t不是最短的,就把t变成j
if(t == n) break;
st[t] = true;
// 用t更新其它点的距离
for(int j = 1; j <= n; j ++)// 迭代其他点
// 用1到t的距离+t到j的距离 去更新 1到j的距离
dist[j] = min(dist[j], dist[t] + g[t][j]);
}
if(dist[n] == 0x3f3f3f) return -1;
return dist[n];
}
int main()
{
scanf("%d%d", &n, &m);
memset(g, 0x3f3f3f, sizeof g);
while(m --)
{
// 添加边
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
// 因为初始化赋值0x3f3f3f
g[a][b] = min(g[a][b], c);
}
int t = dijkstra();
printf("%d\n", t);
return 0;
}
二、堆优化版Dijkstra:(稀疏图:邻接表)(优先队列实现小顶堆)
给定n个点,m条边的有向图,所有边均为正值。
请你求出1号点到n号点的最短距离,如果无法从1号点走到n好点,输出-1.
1 < n,m <= 10^5(稀疏图:邻接表)
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
// 使用堆维护所有点的距离,须要知道结点编号,堆里面存储的是pair
// pair<距离, 编号>
typedef pair<int, int> PII;
const int N = 1e5 + 10;
int n, m;
int h[N], e[N], w[N], ne[N], idx;// 稀疏图:邻接表
int dist[N];// 距离
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()
{
// 设置其他点距离为“正无穷”,1号点距离为0
memset(dist, 0x3f3f3f, sizeof dist);
dist[1] = 0;
// 优先队列实现小顶堆
priority_queue<PII, vector<PII>, greater<PII>> heap;
heap.push({0, 1});// 添加举例为0,编号为1的点
while(heap.size())
{
// 当前没有确定最短路的点中,距离最小的那一个
auto t = heap.top();
heap.pop();
int num = t.second, distance = t.first;// 编号,距离
if(st[num]) continue;// 结点冗余(出现过)
// 用t取更新其他点的距离:遍历这个点的所有邻边
for(int i = h[num]; i != -1; i = ne[i])// 遍历邻接结点
{
int j = e[i];// 取邻接结点“编号”
// 用1到t的距离+t到j的距离 去更新 1到j的距离
if(dist[j] > distance + w[i])
{
dist[j] = distance + w[i];
heap.push({dist[j], j});
}
}
}
if(dist[n] == 0x3f3f3f) return -1;
return dist[n];
}
int main()
{
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
while(m --)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
int t = dijkstra();
printf("%d\n", t);
return 0;
}
三、(负权图)Bellman_Ford算法:有边数限制的最短路
(有边数限制的最短路)
给定n个点,m条边的有向图,图中可能存在重边和自环,注意边权可能是负数。
请你求出从1号点到n号点(最多经过k条边)的最短距离,如不没有输出impossible。
// 给定n个点,m条边的有向图,图中可能存在重边和自环,注意边权可能是负数。
// 请求出从1号点到n号点(最多经过k条边)的最短距离,如果没有,输出impossible。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 500 + 10, M = 1e4 + 10;
int n, m, k;
int dist[N], backup[N];// dist:距离 backup:每一次迭代前的"备份"
// 存储边的信息
// a 到 b 的权值是 w
struct Edge
{
int a, b, w;
}edges[M];
int bellman_ford()
{
memset(dist, 0x3f3f3f, sizeof dist);
dist[1] = 0;
// 最多经过k条边,迭代k次
for(int i = 0; i < k; i ++)
{
memcpy(backup, dist, sizeof dist);// 备份
for(int j = 0; j < m; j ++)
{
int a = edges[j].a, b = edges[j].b, w = edges[j].w;
// 到b的距离 vs 到a的距离+a到b的距离
dist[b] = min(dist[b], backup[a] + w);
}
}
// 最后保证:dist[b] <= back[a] + w
if(dist[n] > 0x3f3f3f / 2) return -1;
return dist[n];
}
int main()
{
scanf("%d%d%d", &n, &m, &k);
// 赋值时,0~m-1,取出时, 0~m-1
for(int i = 0; i < m; i ++)
{
int a, b, w;
scanf("%d%d%d", &a, &b, &w);
edges[i] = {a, b, w};
}
int t = bellman_ford();
if(t == -1) puts("impossible");
else printf("%d\n", t);
return 0;
}
四、SPFA(spfa求最短路,要求没有负环)
思路:(更新过谁,就拿这个点去更新别人)
**思考:推荐使用spfa算法,正权图也可以使用。
注意:如果使用“spfa算法”被卡了(出现“网格形状的图”),那就换成dijkstra算法。
例题1:spfa算法求最短路:
给定n个点,m条边的有向图,图中可能存在重边和自环,注意边权可能是负数。
请你求出从1号点到n号点的最短距离,如果没有输出impossible。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 1e5 + 10;
int n, m;
int h[N], w[N], e[N], ne[N], idx;
int dist[N];
bool st[N];
// 插入边
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
// 使用限制:图中没有负环,可以代替“dijkstra算法”
int spfa()
{
// 将距离全部设置为 “正无穷”
memset(dist, 0x3f3f3f, 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])
{
q.push(j);
st[j] = true;
}
}
}
}
if(dist[n] == 0x3f3f3f) return -1;
return dist[n];
}
int main()
{
// 邻接表的元素全部初始化为 -1
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
// 插边 + 权值
while(m --)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
int t = spfa();
if(t == -1) puts("impossible");
else printf("%d\n", t);
return 0;
}
例题2:spfa算法:《判断是否存在“负环”》
由“抽屉原理”知道:一共有n个点,出现n+1次时,必定有两个点重复出现。
给定n个点,m条边的有向图,图中可能存在重边和自环,注意边权可能是负数。
请你判断图中是否存在“负权回路”。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 1e5 + 10;
int n, m;
int h[N], w[N], e[N], ne[N], idx;// 邻接表
int dist[N], cnt[N];// dist:距离 cnt:边数
bool st[N];// 标记
// 插边
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
bool spfa()
{
queue<int> q;
// q.push(1);
// st[1] = true;
// 注意1:从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;// 更新边数!!!
// 由“抽屉原理”,判断“负环”,边重复出现
if(cnt[j] >= n) return true;
if(!st[j])
{
q.push(j);
st[j] = true;
}
}
}
}
return false;// 循环结束,表示不存在 负环
}
int main()
{
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
while(m --)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
if(spfa()) puts("Yes");
else puts("No");
return 0;
}
五、Floyd算法
:给定n个点,m条边的有向图,图中可能存在重边和自环,注意边权可能是负数。
在给定k个询问,每个询问包含两个整数x和y,表示查询x到y的最短距离,如果路径不存在,则输入出“impossible”。(数据保证图中,不存在负权回路)
1 <= n <= 200, 1 <= m < n^2(稠密图,邻接)矩阵
// 1 <= n <= 200, 1 <= m < n^2(稠密图,邻接)矩阵
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 210, INF = 1e9;
int n, m, Q;
int d[N][N];// 邻接矩阵
// dp优化???
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]);
}
int main()
{
scanf("%d%d%d", &n, &m, &Q);
// 初始化 邻接矩阵
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, w;
scanf("%d%d%d", &a, &b, &w);
d[a][b] = min(d[a][b], w);// 重边取最小
}
floyd();
while(Q --)
{
int x, y;
scanf("%d%d", &x, &y);
// 中间可能会更新,然后比INF小那么“一点点”
if(d[x][y] > INF / 2) puts("impossible");
else printf("%d", d[x][y]);
}
return 0;
}
总结
提示:这里对文章进行总结:
💕💕💕