最短路问题分类 有向图无向图无所谓(无向图建两条边就ok) 重边和自环只有朴素dij和floyd的邻接矩阵需要特判
难点在建图
先确定是哪种图:
稠密图:m~n^2
稀疏图:m~n
补:floyd可以处理负权
边全正
朴素迪杰斯特拉O(n2) 稠密图
用邻接矩阵g[][]存图 初始化0x3f
开一个st[ ]记录确定点的集合
开一个d[ ]记录最短距离 初始化0x3f
#include<bits/stdc++.h>
using namespace std;
const int N=510;
int g[N][N];
int d[N];
int st[N]; 是否在集合内
int n,m;
int dij(){ // 1到n最短路长度
memset(d,0x3f,sizeof(d));
d[1]=0; 先把源点距离初始化0
for(int i=0;i<n;i++){ 循环n次
int t=-1; 每次找不在集合内的最近距离点
for(int j=1;j<=n;j++){
if(!st[j]&&(t==-1||d[j]<d[t])) t=j;
}
st[t]=1; 确定此点
for(int j=1;j<=n;j++){ 根据此点更新 (松弛)
d[j]=min(d[j],d[t]+g[t][j]);
}
}
if(d[n]==0x3f3f3f3f) return -1; 不可达
else return d[n];
}
int main(){
cin>>n>>m;
memset(g,0x3f,sizeof(g)); // 邻接数组初始化为最大值
for(int i=0;i<m;i++){
int x,y,z;
cin>>x>>y>>z;
g[x][y]=min(g[x][y],z); 考虑重边
}
cout<<dij();
}
堆优化迪杰斯特拉O(mlogn) 稀疏图 用邻接表 不用处理重边
注意邻接表的边长w[N]
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N=1000010;
int head[N],e[N],ne[N],w[N],idx; 记得memset h[]
int st[N];
int d[N];
typedef pair<int,int> PII;
void add(int x,int y,int z){ 邻接表带边长w[n]
e[idx]=y; w[idx]=z; ne[idx]=head[x]; head[x]=idx++;
}
int dij(){
memset(d,0x3f,sizeof(d)); d[]数组初始化
d[1]=0;
priority_queue<PII,vector<PII>,greater<PII>> hp; <距离,点>优先队列
hp.push({0,1});
while(!hp.empty()){ 写法类似BFS
auto t=hp.top();
hp.pop(); 快pop
int father=t.second;
if(st[father]) continue; 在集合内了 continue
st[father]=1; 放入集合
for(int i=head[father];i!=-1;i=ne[i]){
int son=e[i];
if(d[son]>d[father]+w[i]){ 需要更新(松弛)的才入队
d[son]=d[father]+w[i];
hp.push({d[son],son});
}
}
}
if(d[n]==0x3f3f3f3f) return -1;
return d[n];
}
int main(){
memset(head,-1,sizeof(head)); 邻接表注意初始化
cin>>n>>m;
while(m--){
int x,y,z; cin>>x>>y>>z;
add(x,y,z);
}
cout<<dij();
}
有负边
Bellman-Ford 算法 外层循环限制最短路的边数
外层循环的k代表从起点经过不超过k条边的最短路距离
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 510, M = 10010;
struct Edge
{
int a, b, c;
}edges[M];
int n, m, k;
int dist[N];
int last[N]; backup
void bellman_ford()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
for (int i = 0; i < k; i ++ ) 边数在k以内
{
memcpy(last, dist, sizeof dist);
for (int j = 0; j < m; j ++ ) 逐边松弛
{
auto e = edges[j];
dist[e.b] = min(dist[e.b], last[e.a] + e.c); 源点用last
}
}
}
int main()
{
scanf("%d%d%d", &n, &m, &k);
for (int i = 0; i < m; i ++ )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
edges[i] = {a, b, c};
}
bellman_ford();
if (dist[n] > 0x3f3f3f3f / 2) puts("impossible"); 不存在是 > 0x3f3f3f3f / 2!!!!
else printf("%d\n", dist[n]);
return 0;
}
负权图 一般用spfa 代码和堆优化dij很像 把握重点在于st数组维护是否在队列中
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 100010;
int n, m;
int h[N], w[N], e[N], ne[N], idx; 记得memset h[]
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 spfa()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
queue<int> q; 普通队列
q.push(1); 存入下标
st[1] = true; 在队列中 则true!!!!
while (q.size()) 类似BFS
{
int t = q.front();
q.pop(); 立即pop()
st[t] = false; 不在队列中了 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;
}
}
}
}
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 = spfa();
if (t == 0x3f3f3f3f) puts("impossible");
else printf("%d\n", t);
return 0;
}
判断有无负环 就用spfa! 此时不再需要typically初始化d[N]; d[1]=0; 并且一开始所有点都要入栈 用cnt记录最短路上的边数 若cnt[i]>=n 那么有负环
#include<bits/stdc++.h>
using namespace std;
const int N=2010;
const int M=10010;
int h[N],e[M],ne[M],w[M],idx; 记得memset h
int st[N],cnt[N],d[N]; 多了一个cnt存边数
int n,m;
void add(int a,int b,int c){
e[idx]=b; w[idx]=c; ne[idx]=h[a]; h[a]=idx++;
}
int spfa(){ 无需初始化d d[1]
queue<int> Q;
for(int i=1;i<=n;i++){ 所有点都入队
st[i]=1;
Q.push(i);
}
while(Q.size()){
int father=Q.front();
Q.pop();
st[father]=0;
for(int i=h[father];i!=-1;i=ne[i]){
int son=e[i];
if(d[son]>d[father]+w[i]){
d[son]=d[father]+w[i];
cnt[son]=cnt[father]+1; 松弛时更新边数
if(cnt[son]>=n) return 1; 有边数>=n 则有负环
if(!st[son]){
Q.push(son);
st[son]=1;
}
}
}
}
return 0; 没用
}
int main(){
memset(h,-1,sizeof(h));
cin>>n>>m;
while(m--){
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
if(spfa()) cout<<"Yes";
else cout<<"No";
}
多源汇 Floyd O(n3) 可以处理负边 用邻接矩阵存图 注意不存在路径的条件
#include<bits/stdc++.h>
using namespace std;
const int N=210;
int INF=1e9; INF=1e9
int d[N][N];
int n,m,k;
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(){
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;
cin>>a>>b>>c;
d[a][b]=min(d[a][b],c);
}
floyd();
while(k--){
int a,b; cin>>a>>b;
int t=d[a][b];
if(t>INF/2) cout<<"impossible"<<endl; t>INF/2 说明不存在
else cout<<t<<endl;
}
}