最短路
单源最短路
N表示图中点的数量 M表示边的数量
1.所有边的权重都是正数
a.朴素Dijkstra算法
时间复杂度 O(N^2) 适合稠密图(M与N^2一个级别 M<=N^2)
求从1号点到其他所有点的距离
1.初始化距离
dist[1]=0,dist[i]=inf
2.迭代
用s存当前已经确定最短距离的点
for(i:0~n 循环n次){
t = 不在s中的距离最近的点 !st[j]&&(t==-1||dist[t]>dist[j])
s中加入t(点t被确定) st[t]=true;
用t更新其他点的距离 dist[j]=min(dist[j],dist[t]+g[t][j]); t做跳板
w 是t到x的距离
}
练习:acwing849
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 510;
int n,m;
int g[N][N];//邻接矩阵存图
int dist[N];//存点1到点i的最短距离
bool st[N];//标记哪些点在集合中
int dijkstra()
{
memset(dist,0x3f,sizeof dist);
dist[1]=0;
for(int i=0;i<n;i++)
{
int t=-1;
for(int j=1;j<=n;j++)
if(!st[j] && (t==-1||dist[t]>dist[j]))//遍历所有点 找到剩下的点中最短的那一个 加入集合S
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; //memset中写的0x3f 实际初始化为0x3f3f3f3f!!!
return dist[n];
}
int main()
{
cin >> n >> m;
memset(g,0x3f,sizeof g);
while (m -- ){
int a,b,c;
cin >> a>>b>>c;
g[a][b]=min(g[a][b],c);
}
int res=dijkstra();
printf("%d\n",res);
return 0;
}
b.堆优化的Dijkstra算法
时间复杂度 :O(M log N) 适用于稀疏图
算法步骤:1.初始化 2.找未进入S中的最近点进入S
3.用S中的点对新进入的点进行松弛操作。
堆可以把第二步优化成O(1) 。有点像宽搜
练习 acwing850
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 1e5+5e4+10;
typedef pair<int, int> PII;
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)// 添加一条边a->b,边权为c
{
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx,idx++;
}
int dijkstra() // 求1号点到n号点的最短路距离,如果从1号点无法走到n号点则返回-1
{
memset(dist,0x3f,sizeof dist);
dist[1]=0;
priority_queue<PII,vector<PII>,greater<PII>> heap;
heap.push({0,1});//加入1号点,距离是0
while(heap.size()){
PII 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])//如果 1->j的距离 > 1->ver的距离+ver->j的距离
{
dist[j]=distance+w[i];
heap.push({dist[j],j});
}
}
}
if(dist[n]==0x3f3f3f3f)return -1;
else return dist[n];
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
for(int i=0;i<m;i++){
int x,y,z;
cin>>x>>y>>z;
add(x,y,z);
}
int res=dijkstra();
cout<<res<<"\n";
return 0;
}
2.存在负权边
a.Bellman-Ford 算法
求不超过k条边的最短路
用结构体存边 遍历结构体即可
时间复杂度:O(nm)
思路:for (n次)
for(所有边 a b w)//a->b 权重w
dist[b]=min(dist[b],dist[a]+w);
//可证明 dist[b]<=dist[a]+w
练习:acwing853 有边数限制的最短路
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 510,M = 1e5+10;
struct Edge{
int a,b;
int w;
}edges[M];
int n,m,k;
int dist[N],backup[N];
int bellman_ford()
{
memset(dist,0x3f,sizeof dist);
dist[1]=0;
for(int i=1;i<=k;i++)//最多经过k条边
{
memcpy(backup,dist,sizeof dist);//备份上一次的dist 用上一次的进行松弛操作
for(int j=0;j<m;j++)//所有边
{
int a=edges[j].a,b=edges[j].b,w=edges[j].w;
dist[b]=min(dist[b],backup[a]+w);
}
}
if(dist[n]>0x3f3f3f3f/2)return -1;//不用== 因为有负权边
return dist[n];
}
int main()
{
cin >> n>>m>>k;
for(int i=0;i<m;i++){
int a,b,w;
cin>>a>>b>>w;
edges[i].a=a;edges[i].b=b;edges[i].w=w;
}
int res=bellman_ford();
if(res==-1)puts("impossible");
else
cout<<res<<"\n";
return 0;
}
b.SPFA
一般情况下:O(m) 最坏O(nm)
思路:优化 bellman_ford 算法。只可能用松弛过的点去松弛其他的点
queue <-- 1
while(queue not empty){
1.t=q.front; q.pop
2.更新t的所有出边 t-w->b
queue <–b
}
练习:AcWing 851. spfa求最短路
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 1e5+10;
int n,m;
int h[N], e[N], w[N], ne[N], idx;
int dist[N];
bool st[N];//标记点 i 是否在队列中,防止队列中存重复的点
queue<int> q;//存储所有待更新的点
int spfa() // 求1号点到n号点的最短路距离,如果从1号点无法走到n号点则返回-1
{
memset(dist,0x3f,sizeof dist);
dist[1]=0;
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 -1;
return dist[n];
}
void add(int a, int b, int c) // 添加一条边a->b,边权为c
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
int main()
{
cin>>n>>m;
memset(h, -1, sizeof h);
for(int i=0;i<m;i++)
{
int a,b,c;
cin>>a>>b>>c;
add(a, b, c);
}
int res=spfa();
if(res==-1)puts("impossible");
else cout<<res<<"\n";
return 0;
}
练习:AcWing 852. spfa判断负环
多源汇最短路
Floyd算法
时间复杂度:O(N^3)
最外层循环是k
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dij = min ( dij , dik + dkj );
练习:AcWing 854. Floyd求最短路
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 210;
const int inf = 1e9;
int n,m,q;
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()
{
cin>>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;
}
}
//输入m条边
while (m -- ){
int x,y,z;
cin>>x>>y>>z;
d[x][y]=min(d[x][y],z);
}
floyd();
//q次询问
while(q--){
int x,y;
cin>>x>>y;
if(d[x][y]>inf/2)puts("impossible");
else printf("%d\n",d[x][y]);
}
return 0;
}