首先,最短路径问题在树以及树的遍历:宽度优先搜索BFS c++的代码实现-CSDN博客中的最后一道例题中讲过,但这只是最短路径问题中边权为1的特殊的分支之一。读者可以在阅读本篇之前思考一下,如果边权不为1的时候该怎么编码,只用普通的BFS(queue)可以实现吗?
最短路径问题分很多种,其中最为特殊的也就是边权为1的最短路径问题,其次,还有边权为0或1的,那么这时候就需要要用到双端队列了(deque),但这并不是本篇的主题。本篇要讲的是边权为3,6,4之类的最短路径问题,也就是BFS(宽度优先搜索)+优先队列。
其实BFS(宽度优先搜索)+优先队列我们也可以将其看作是Dijkstra算法,但两者之间还是有些许的区别,当优先队列中的优先级是距离起点的最小值时,那么BFS(宽度优先搜索)+优先队列=Dijkstra算法,我们甚至可以延申出A*算法以及IDA*算法等。
最短路径问题也有很多解法,但最主要的解法还是BFS(宽度优先搜索)+优先队列。接下来,我们以下图为例。
(图片来源:shangxueba.com)
我们以a为起点,g为终点,那么a与g的最短路径是那一条呢?
我们先想一下,是否可以使用贪心最优搜索呢?每次都走当前边权最短的路径,一直到终点g。但是贪心最优搜索的缺点很明显,不一定能得到正确答案。比如当走到c时,下一个选择走的路径是d,而不是f,但这并不是最优解。毕竟运用了贪心算法,只看当下,不管未来,而且选择后不会回头。所以尽管贪心最优路径的效率很高,但结果不一定正确。
那用BFS(宽度优先搜索)+优先队列怎样来解呢?
其实很简单,在前面也说了,优先队列的优先级是当前节点距离起点的距离,也就是Dijkstra算法。我们只需要从起点开始扩展邻节点,并将邻节点的路径重新扫描一次,如果更短则更新该邻节点的优先级,也就是该邻节点距离起点的最短距离。
比如,在最开始时a入队,扩展邻节点b,e,d,距离分别为1,9,5,a出队。接着选择距离起点最短的b,再扩展新节点c,距离为5,b出队。接下来最短的是c,扩展新节点g,f,距离为15,11,再判断旧节点e,距离为12,大于e的距离,舍去,c出队。然后经过一系列的操作后,最后,当f为队顶时,由于终点g的距离更新为14,得出答案。
其实,如果只是求一个节点的最短路径用A*算法是最快的,毕竟A*算法用到了估计函数。但如果是求全部节点的最短路径,那么Dijkstra算法比A*更好。
讲完原理后我们以蓝桥杯的一道例题为例。
最短路径
问题描述:给出一个图,求1到其他所有点的最短路径。
输入:第1行输入正整数n,m,n为点的数量,m为边的数量;第2~m+1行中,每行输入三个数字u,v,w,表示u到v有一条长度为w的单向边。
这题是典型的BFS+优先队列的题目,该题与上述基本相等,可以直接编写,以下是代码。
#include<iostream>
#include<queue>
using namespace std;
#define N 100010
const long long INF=0x3F3F3F3F3F3F3F;
struct dot{
int s,t;
long long w;
void full(int a,int b,long long w){
s=a;
t=b;
w=w;
}
};
struct node{
int id,n_dis;
void fill(int i,int dis){
id=i;
n_dis=dis;
}
bool operator <(node &a){
return this->n_dis > a.n_dis;
}
};
vector<dot> mp[N];
long long dis[N];
bool vis[N];
void Dijkstra(){
priority_queue<node> q;
int s=0;
dis[s]=0;
vis[s]=true;
node now;
now.fill(s,dis[s]);
q.push(now);
while(!q.empty()){
now=q.top();
q.pop();
if(vis[now.id])continue;
vis[now.id]=true;
for(int i=0;i<mp[now.id].size();i++){
dot u=mp[now.id][i];
if(vis[u.t])continue;
if(dis[u.t]>u.w+now.n_dis){
dis[u.t]=u.w+now.n_dis;
node x;
x.fill(u.t,dis[u.t]);
q.push(x);
}
}
}
}
int main(){
int n,m;
cin>>n>>m;
for(int i=0;i<n;i++){
vis[i]=false;
dis[i]=INF;
}
for(int i=0;i<m;i++){
int a,b,w;
dot p;
p.full(a,b,w);
cin>>a>>b>>w;
mp[a].push_back(p);
}
Dijkstra();
for(int i=0;i<n;i++){
if(dis[i]>=INF)cout<<"-1";
else cout<<dis[i]<<" ";
}
return 0;
}
其中,21行的<号的定义必不可少,因为STL提供的priority_queue,系统的优先级是按照从小到大的,我们只需要将<的定义重载为>就好了。如果理解不了的,也可以将<的重载删去,再在后面直接使用priority_queue<node,vector<node>,greater<node>>代替。
vis数组是判断该点是否走到最短路径的布尔数组。mp则是用来存储图的。总体来说编码不难。
如果我没有记错的话,最短路径问题还可以用动态规划,但蒟蒻不太熟悉动态规划,所以在这不多介绍,感兴趣的家人们可以上网找一下。
打字不易,家人们支持一下吧,本宝宝求求了。
(侵删)