1)时间复杂度
a)单源最短路
边权均为正:dijkstra朴素算法 O( n^2 )
堆优化dijkstra算法 O( mlogn )
边权有负: spfa算法 一般O( m ) 最坏O( mn )
bellman_ford O( mn )
b)多源汇最短路
floyd算法 O( n^3 )
2)dijkstra算法求最短路
a) 朴素算法
当顶点的数量较少时(一般小于5000),利用数组g存储图的结构。
存储边之前先将数组g的所有元素置为一个很大的值(0x3f3f3f3f),然
后在输入边的时候每次与已存的值比较取最小值。
定义一个距离数组d,所有元素初始化为一个很大的值(0x3f3f3f3f),
然后将d[1]置为0。
定义一个bool类型的辅助数组st,用来判断点是否已加入路径。
接下来就是循环了,外层循环n次,内层循环n次,内层循环中,遍历距离
数组,每次找到一个与初始点(这里默认为1,其他的对应修改即可)距离最
小的点t,然后将辅助数组st对应的点置为true,再利用该点去更新距离数
组d。(d[j]=min(d[j],d[t]+g[t][j])
#include<iostream>
using namespace std;
#include<cstring>
const int N=510;
int g[N][N];
int d[N];
int n,m;
bool st[N];
int dijkstra()
{
memset(d,0x3f,sizeof d);
d[1]=0;
for(int i=1;i<=n;i++)
{
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()
{
memset(g,0x3f,sizeof g);
cin>>n>>m;
while(m--)
{
int a,b,w;
cin>>a>>b>>w;
g[a][b]=min(g[a][b],w);
}
cout<<dijkstra();
return 0;
}
***小提示:
输入输出数据较多时可以用scanf和printf输入输出,可以节省很多时间,别忘了加头文件!
b)堆优化
当顶点数与边数相差不大时可使用堆优化法
存储依然使用数组,用数组模拟邻接表,定义四个数组,h,e,ne,w
h:存储表头结点值
e:存储所连的点的值
ne:连向同一个头节点的下一个结点值
w:边权值
堆优化可以使用手写堆(更加灵活),手写堆链接https://blog.csdn.net/qq_62654838/article/details/126794049?spm=1001.2014.3001.5502,也可以使用stl容器里的优先队列priority_queue,这里采用stl容器,注意stl里默认的是最大堆,最小堆要写成priority_queue<pair<int,int>,vector<pair<int,int>,greater<pari<int,int>>
使用一个辅助数组st,判断点是否已经存在,用来去掉重边
先将第一个点放进去,即将{0,1}第一个点存进heap
当堆不空时,每次从中取出堆顶,并将堆顶删除,判断取出的点是否存在,不存在就加入路径,并用该点堆d数组进行更新,当需要更新的时候,将更新的边加入堆中
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N=3*100010;
int d[N];
bool st[N];
int e[N],ne[N],h[N],w[N],idx;
typedef pair<int,int>pll;
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 dijkstra()
{
memset(d,0x3f,sizeof d);
d[1]=0;
priority_queue<pll,vector<pll>,greater<pll>>heap;
heap.push({0,1});
while(heap.size())
{
pll t=heap.top();
heap.pop();
int distance = t.first,ver = t.second;
if(st[ver]) continue;
st[ver] = 1;
for(int i=h[ver];i!=-1;i=ne[i])
{
int j=e[i];
int dd=distance+w[i];
if(d[j]>dd)
{
d[j]=dd;
heap.push({d[j],j});
}
}
}
if(d[n]==0x3f3f3f3f)
{
return -1;
}
else return d[n];
}
int main()
{
memset(h,-1,sizeof h);
cin>>n>>m;
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
cout<<dijkstra();
return 0;
}
3)spfa算法求最短路
当边数和顶点数相差不大时,使用spfa算法
采用数组模拟邻接表,借助队列,初始化后将第一个点存入队列,然后循环操作,这里要借助bool数组book来判断队列中是否存在该点,当队列不空时,每次取出队首并出队,并将该点的book置为0,然后用该点去更新距离数组dist,每当有一个距离被更新时,尝试将该点加入队列,先判断该点是否在队列中,若不在则将该点入队。
最后在判断最短路是否存在时是直接和初始化的较大的值去比较是因为这里的存储方式是邻接表,去除了不相连的点加入的情况,只有相连的点才可能被加入。
#include<iostream>
using namespace std;
#include<cstring>
#include<queue>
const int N=100101;
int h[N],e[N],ne[N],w[N],idx;
int dist[N];
bool book[N];
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()
{
memset(dist,0x3f,sizeof dist);
dist[1]=0;
queue<int>q;
q.push(1);
book[1]=1;
while(q.size())
{
int t=q.front();
q.pop();
book[t]=0;
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(!book[j])
{
q.push(j);
book[j]=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=spfa();
if(t==0x3f3f3f3f) cout<<"impossible";
else cout<<t;
return 0;
}
4) bellman_ford算法求最短路
bellman_ford算法用来求有边数限制的最短路(在确定的边数内连通的最短路)
这里的存储图的方式采用结构体形式,结构体有三个元素,边的两个顶点和边权。
外层循环k(边数限制数)次,内层循环边数次,每次找到距离初始点最小的点并更新距离数组d,这里要注意的是,一定要用备份的距离backup数组去比较更新(因为这是有边数限制的)
注意这里在最后判断是否存在最短路时是与一个较大的值比较,而不是与初始化的那个很大的值进行比较,因为可能存在负边将边距更新了,但实际上是到不了的(前面有的点到不了)。这里就和spfa算法区别开了,因为这里的存储方式是结构体数组,不能排除加入不相连的情况,所以这里不能与初始化的较大的值去比较。
#include<iostream>
#include<cstring>
using namespace std;
const int N=510,M=10010;
int d[N],backup[N];
struct edge{
int a,b,w;
}edges[M];
int n,m,k;
int flag=1;
int bellman_ford()
{
memset(d,0x3f,sizeof d);
d[1]=0;
for(int i=0;i<k;i++)
{
memcpy(backup,d,sizeof d);
for(int j=0;j<m;j++)
{
int a=edges[j].a,b=edges[j].b,c=edges[j].w;
d[b]=min(d[b],backup[a]+c);
}
}
if(d[n]>0x3f3f3f3f/2)
{
flag=0;
return -1;
}
else return d[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,b,w};
}
int t=bellman_ford();
if(flag==0) cout<<"impossible";
else cout<<t;
return 0;
}
5)floyd算法求最短路
当需要求任意两个点之间的最短路时使用Floyd算法(顶点数一般比较少,边数较多,用邻接矩阵存储)
采用三层循环的方式,直接依次更新距离 g[i][j]=min(g[i][j],g[i][k]+g[k][j]),邻接矩阵的元素就是两个点间的距离
在初始化的时候,自身到自身的距离是0,其他初始化为一个较大的值
注意在判断最短路的时候也是和一个较大但是比初始化的较大值小一些的值比较
#include<iostream>
using namespace std;
#include<cstring>
#include<stdio.h>
const int N=210;
int g[N][N];
int n,m,k;
const int INF=0x3f3f3f3f;
void floyd()
{
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
g[i][j]=min(g[i][j],g[i][k]+g[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) g[i][j]=0;
else g[i][j]=INF;
}
while(m--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
g[a][b]=min(g[a][b],c);
}
floyd();
while(k--)
{
int x,y;
scanf("%d%d",&x,&y);
if(g[x][y]>INF/2) puts("impossible");
else printf("%d\n",g[x][y]);
}
return 0;
}