目录
BFS算法主要用来解决无权图的单源最短路径问题,Dijkstra算法只要解决带权图的单源最短路径问题,Floyd算法主要解决多源最短路径问题。
最短路径问题主要针对有向图而言,主要应用有导航选择时间,或者价格,或者步行最少的路径。(区别于最小生成树:一般针对无向图)
单源最短路径:一个点到其他所有点的最短路径
多源最短路径:图中任意两个点的最短路径
BFS算法
时间复杂度:O(V²),图的存储采用邻接矩阵
改编BFS遍历,主要是访问顶点的时候需要对顶点的dist[]数组进行处理
具体代码
#include<bits/stdc++.h>
#include<queue>
#define MAX 0x3f
#define INF 0x3f3f3f3f
using namespace std;
const int N=10010;
int x; //从x顶点开始
int m,n; //m个顶点,n条边
int g[N][N];
int dist[N];
int path[N]; //用来记录路径(逆序记录)
bool st[N];
queue<int> q;
void BFS(int x)
{
q.push(x);
st[x]=true;
dist[x]=0;
while(!q.empty())
{
int k=q.front();
q.pop();
for(int i=1;i<=m;i++)
{
if(!st[i]&&g[k][i]==1) //不能写成g[k][i],因为g[][]的值是1或者INF,没有0
{
dist[i]=dist[k]+1;
st[i]=true;
path[i]=k;
q.push(i);
}
}
}
}
int main()
{
memset(path,-1,sizeof path);
memset(g,MAX,sizeof g);
memset(dist,MAX,sizeof dist);
memset(st,false,sizeof st);
freopen("B.txt","r",stdin);
cin>>x;
cin>>m>>n;
while(n--)
{
int a,b;
cin>>a>>b;
g[a][b]=min(g[a][b],1);
}
BFS(x);
for(int i=1;i<=m;i++)
{
if(dist[i]==INF)
{
cout<<"-1"<<" ";
}
else cout<<dist[i]<<" ";
}
cout<<endl;
int k=8;
cout<<k<<" ";
while(path[k]!=-1) //逆序输出1->8的最短路径经过哪些点
{
cout<<path[k]<<" ";
k=path[k];
}
cout<<endl;
return 0;
}
Dijkstra算法
基本思想
两个点之间的最短路径包含了两点之前其他点的最短路径。
用dist[]数组来记录每个点当前到源点的最短距离,从没有确定最短路径的点中找到当前到源点距离最短的点(当前dist[]最小的点)加入到已经确定最短路径的集合中(基于贪心),每次有新的点加入之后,都需要根据新加入的点更新dist[]数组,这样每次选择加入的点就能确定是已经是最短路径。
具体代码
//1->n的最短距离
#include<bits/stdc++.h>
#define MAX 0x3f
#define INF 0x3f3f3f3f
using namespace std;
const int N=20020;
int g[N][N];
int dist[N];
int path[N]; //记录路径
bool st[N];
int m,n; //m个顶点,n条边
int Dijkstra()
{
st[1]=true; //从源点开始,将源点加入
for(int i=1;i<=m;i++) //根据源点修改dist[]数组和path[]数组
{
if(g[1][i]!=INF)
{
dist[i]=min(dist[i],g[1][i]);
path[i]=1;
}
}
for(int i=1;i<m;i++)
{
int k=-1;
for(int j=1;j<=m;j++)
{
if(!st[j]&&(k==-1||dist[k]>dist[j]))
{
k=j;
}
}
st[k]=true;
for(int j=1;j<=m;j++)
{
if(!st[j])
{
if(dist[j]>dist[k]+g[k][j])
{
dist[j]=dist[k]+g[k][j];
path[j]=k;
}
}
}
}
if(dist[m]==0) return INF;
return dist[m];
}
int main()
{
path[1]=-1;
memset(g,MAX,sizeof g);
memset(dist,MAX,sizeof dist);
memset(st,false,sizeof st);
freopen("B.txt","r",stdin);
scanf("%d%d",&m,&n);
while(n--)
{
int a,b,c;
cin>>a>>b>>c;
g[a][b]=min(g[a][b],c);
}
int t=Dijkstra();
if(t!=INF) cout<<t<<endl;
else cout<<"不存在最短距离"<<endl;
int a=m; //逆序输出从1->n的路径
cout<<a<<" ";
while(path[a]!=-1)
{
cout<<path[a]<<" ";
a=path[a];
}
return 0;
}
时间复杂度:O(V²)
和Prim算法(最小生成树(MST)Prim算法和Kruskal算法-CSDN博客)的代码特别像,但是主要区别在Prim算法的lowcost[]数组记录的是当前没有处理的点到当前MST的最短距离。
for(int j=1;j<=m;j++)
{
lowcost[j]=min(lowcost[j],g[k][j]); //更新没有加入的点的最小代价
}
而Dijkstra算法的dist[]数组记录的是当前没有确定最短路径的点到到源点的最短距离。
for(int j=1;j<=m;j++)
{
if(!st[j])
{
if(dist[j]>dist[k]+g[k][j])
{
dist[j]=dist[k]+g[k][j];
path[j]=k;
}
}
}
Floyd算法
基本思想
对应任意的两个点vi,vj,可以遍历所有的顶点,让顶点(例如顶点vk)在这两个点之间中转,即vi,vj通过vk能否找到长度更短的路径(即比较g[i][j]和g[i][k]+g[k][j]),如果g[i][j]>g[i][k]+g[k][j],就可以更新g[][]数组,这样不断加入新的中转点,g[][]数组也会在递推过程中不断被更新,当所有的点都参与中转之后得到的g[][]数组中记录的内容就是任意两点之间的最短路径长度。
具体代码
时间复杂度:O(V³),所有点都需要中转,并且每次中转都需要改变g[][]数组中任意两点之间的当前最短路径长度,即需要遍历数组。
#include<bits/stdc++.h>
#define MAX 0x3f
#define INF 0x3f3f3f3f
using namespace std;
const int N=210;
int g[N][N]; //一开始是邻接矩阵,后来是经过中转之后当前的最短距离
int path[N][N];
int m,n,k;
void Floyd()
{
for(int k=1;k<=m;k++)
{
for(int i=1;i<=m;i++)
{
for(int j=1;j<=m;j++)
{
if(g[i][j]>g[i][k]+g[k][j])
{
g[i][j]=g[i][k]+g[k][j];
path[i][j]=k;
}
}
}
}
}
int main()
{
memset(g,MAX,sizeof g);
memset(path,-1,sizeof path);
freopen("B.txt","r",stdin);
scanf("%d%d%d",&m,&n,&k);
for(int i=1;i<=m;i++) //一开始邻接矩阵的对角线全是0,而不是无穷
{
for(int j=1;j<=m;j++)
{
if(i==j) g[i][j]=0;
else g[i][j]=INF;
}
}
while(n--)
{
int x,y,w;
scanf("%d%d%d",&x,&y,&w);
g[x][y]=min(g[x][y],w);
}
Floyd();
while(k--)
{
int st,ed;
scanf("%d%d",&st,&ed);
if(g[st][ed]<INF/2) cout<<g[st][ed]<<endl;
else cout<<"-1"<<endl;
}
return 0;
}
注意:初始化g[][]的时候对角线要初始化为0,其他点为INF,有别于其他用到邻接矩阵的算法
g[][]一开始存储的是有向图的邻接矩阵,经过算法递推之后最终变成任意两点之间的最短距离。
至此,最短路算法告一段落。。。