最短路径
- 朴素dijkstra
- 思路:
第一步先初始化dist[1] = 0,dist[i] = INF (dist数组表示某一点到起点的距离);
第二步第一层循环for循环 1 - n遍历所有点,然后第二层循环for 1 - n,找到 st[t] 值为 false 且距离起点最近的点的下标值赋给变量 t 并且把st[t]的值设为true( st 数组是标记这个点的最短路径是否已经找到,找到则为true);
第三步用 t 更新其它所有点的距离。 - 例题:
- 代码实现:
#include <bits/stdc++.h>
using namespace std;
const int N = 510;
int n,m;
int g[N][N]; //存储每一条边的距离(邻接矩阵适合稠密图)
int dist[N]; //存储每一个点到起点的距离
bool st[N]; // 标记某一点的最短路径是否已经确定下来
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
for(int i = 1; i <= n; i ++ )
{
int t = -1;
for(int j = 1; j <= n; j ++ )
{
if(!st[j] && (t == -1 || 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]);
}
}
if(dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
int main()
{
scanf("%d%d",&n,&m);
memset(g, 0x3f, sizeof g);
for(int i = 1; i <= m; i ++ )
{
int a,b,w;
scanf("%d%d%d", &a, &b, &w);
g[a][b] = min(g[a][b], w);
}
int res = dijkstra();
printf("%d",res);
return 0;
}
- 堆优化版dijkstra:
- 优化了找最小值所需的时间;
- 例题:
- 代码实现:
#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;//<距离起点的距离,节点编号>
const int N = 150010;
int n,m,idx;//当前指到哪个邻接点
int h[N],w[N],e[N],ne[N];//e数组是存储
int dist[N];
bool st[N];
//在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;//1号节点距离起点为0
priority_queue<PII, vector<PII>, greater<PII>> heap;//定义小根堆
heap.push({0,1});//将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] > distance + w[i])
{
dist[j] = distance + w[i];
heap.push({dist[j],j});
}
}
}
if(dist[n] == 0x3f3f3f3f) 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 res = dijkstra();
printf("%d\n",res);
return 0;
}
- Bellman_ford:
- 思路:
图用结构体实现,算法有两层循环实现,循环之前要初始化,第一层循环n次,第一层的含义是循环了几条边;第二层循环循环m次,更新距离,这里有个关键点就是要备份数组,用备份的数组去更新距离。
备份在第一与第二层循环之间备份。 - 例题:
- 代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 510, M = 10010;
int n,m,k;
int dist[N];//意义跟上面dijkstra的一样
bool st[N];
int backup[N];//备份数组
struct edge{
int a,b,w;
}edges[M];
int bellman_ford()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
for(int i = 0; i < k; i ++ )//因为题目限制不超过k条边
{
memcpy(backup,dist,sizeof dist);//一定要记得备份
for(int j = 0; j < m; j ++ )
{
int a = edges[j].a;
int b = edges[j].b;
int w = edges[j].w;
dist[b] = min(dist[b], backup[a] + w);
}
}
if(dist[n] > 0x3f3f3f3f / 2) return -2;
return dist[n];
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
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 == -2) puts("impossible");
else printf("%d\n",t);
return 0;
}
- SPFA(基本可以解决dijkstra的题,但是如果卡住只能用堆优化的dijkstra;SPFA还可以判断是否有负权回路)
- 思路:
对bellman_ford的优化就是用队列实现只对需要更新的点进行更新,代码类似堆优化的dijkstra,只是st数组标注的是点是否已经在队列中,队列存的是待更新的点。 - 例题:
- 代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int h[N],w[N],e[N],ne[N],idx;
int dist[N];
bool st[N];//标志这个点是否已经在队列中
int n,m;
void add(int a,int b,int c)
{
e[idx] = b;
ne[idx] = h[a];
w[idx] = c;
h[a] = idx ++ ;
}
int 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])
{
q.push(j);
st[j] = true;
}
}
}
}
if(dist[n] == 0x3f3f3f3f) return -2;
return dist[n];
}
int main()
{
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
for(int i = 0; i < m; i ++ )
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
int res = spfa();
if(res == -2) puts("impossible");
else printf("%d\n",res);
return 0;
}
- Floyd:
- 思路:
基于动态规划,三维的数组实现。d[ i ] [ j ] [ k ],表示从 i 开始,经过1 - k个点到达 j 的最短距离,只是最后k被优化掉了,只剩下二维。 - 例题:
- 代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 210, INF = 1e9;
int n,m,k;
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] + d[k][j]);
}
int main()
{
scanf("%d%d%d",&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;//这里的初始化有区别与上面几种算法,因为它是多源的
for(int i = 0; i < m; i ++ )
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
d[x][y] = min(d[x][y],z);//存最小的边即可
}
floyd();
while(k -- )
{
int x,y;
scanf("%d%d",&x,&y);
if(d[x][y] > INF / 2) puts("impossible");//因为有负的,所以只需大于 INF / 2
else cout << d[x][y] << endl;
}
return 0;
}
以上是个人的一些看法,如果有错误的或者更好的做法,欢迎评论区给予更正谢谢!