最短路径1(dijkstra):
1.什么是最短路径?
意思是在一张带权图上求解给定的出发点和终点之间的最短路径长度
其中出发点称之为源点,终点称之为汇点
根据给定的源点数量又分为:
- ①单源最短路ssp
- ②多源最短路
举个例子,存在图:
定义点1为出发点,点5为终点,则点1称之为源点,点5称之为汇点。属于单源最短路。
从1到5的路径有三条,分别是1->2->5;1->3->2->5;1->4->5。
显然这三条路径所经过的边权和不一样,1->2->5边权和为15,而1->3->2->5边权和为10,1->4->5边权和为11。发现,1->3->2->5这条路径更优。因此,1->3->2->5是该例图的一条最短路径。
求解最短路径的方法
对于单源最短路,我们必须得从源点出发,从1可以抵达点2、3、4,我们可以使用一个数组d[ i ]来记录从源点1到其他各点的权值和,显然d[2]=2,d[3]=10,d[4]=3。
思考:接下来我们应该怎么办呢?
如果借鉴广搜的思路,我们就接着枚举点2,将点2标记为true;然后再枚举点3,然而此时点3已经无法再访问标记为true的点2了。
我们可以发现从1->3->2的路径却可以让抵达点2的路径权值和变得更小,但是点2已经标记为true,点2的更优路径无法更新,怎么办呢?
思考:点2、3、4我们应该优先访问谁?
我们在思考这个问题时,我们要带着为什么要有先后访问的顺序呢?
当然就是为了取得更优的路径。1->2的权值为10,为了能找到其他的路径抵达点2能有更短的路径,我们应该选择哪一个点执行枚举呢?
显然,我们必须找到一个点,从点1抵达该点的权值和要比1->2的权值和小,才有可能更新出更优的解。所以我们应该优先去遍历点3,从点3去拓展其他更优的解。
而我们可以发现对于点2,我们需要先访问比点2更优的点,同样地对于其他的点3、4也需要优先访问比他们更优的点。那岂不就是找一个所有的点里面最优(最小)的那一个点吗?
我们可以发现,我们每一次向外拓展路径,都选择权值和最小的那一个点进行拓展,因为只有这样才能取得更优的路径。
比如从点1出发,访问了点2,3,4,d[2]=10,d[3]=2,d[4]=6,点1的所有邻接点访问结束,点1标记为true。从刚才得到的结论,我们选择三个点中权值和最小的点3,d[3]=2,看看能不能找到其他节点的更优路径。
所以我们寻找最短路径的第一步:
①选取所有未访问结点中权值和最小的点执行访问
int t=d[0];
int u;
for(int j=1;j<=n;j++){
if(vis[j]==0&&d[j]<t){
t=d[j];
u=j;
}
}
然后访问点3的邻接节点,发现:d[3]+w[3,2]=2+3=5 < d[2]
这意味着什么?
很显然1->3->2是抵达点2的更优路径!!!
因此我们可以更新d[2]=d[3]+w[3,2];
寻找最短路径的第二步:
②以该点为圆心进行拓展,更新其邻接节点的更优路径
for(int i=0;i<g[u].size();i++){
int v=g[u][i].v;
int w=g[u][i].w;
if(d[v]>d[u]+w)d[v]=d[u]+w;//松弛操作
}
点3的所有邻接节点访问结束,此时点3是否需要标记为true呢?
思考:还会存在其他的路径是的d[3]更小吗?
在刚才的步骤中,点3是被我们选出来的,所有未被标记为true的结点中,权值和最小的点,假如存在另外的点v,使得d[v]+w[v,3]<d[3],那么一定满足d[v]<d[3],然而d[3]是最小值,所以点v不知能存在。
也就是说,点1抵达点3的最小值已经确定为d[3],不会再更新更优解。因此我们可以将点3标记为true,以后不再需要访问他了。
寻找最短路径的第三步:
③将当前访问的点标记为true,表示其路径已达最优
vis[u]=1;
然后重新回到步骤①,继续选取未标记结点中的最小点进行访问,循环此三步即可。
##总结步骤
- ①选取所有未访问结点中权值和最小的点执行访问
- ②以该点为圆心进行拓展,更新其邻接节点的更优路径
- ③将当前访问的点标记为true,表示其路径已达最优
- 返回点①继续循环访问
完整代码:
void dijkstra(int st){
memset(d,127,sizeof(d));
d[st]=0;//起点出发
for(int i=1;i<=n;i++){
int t=d[0];
int u;
for(int j=1;j<=n;j++){
if(vis[j]==0&&d[j]<t){
t=d[j];
u=j;
}
}
for(int i=0;i<g[u].size();i++){
int v=g[u][i].v;
int w=g[u][i].w;
if(d[v]>d[u]+w)d[v]=d[u]+w;//松弛操作
}
vis[u]=1;
}
}
思考:执行步骤①时,暴力解法耗时O(n),可以用什么方法优化呢?
步骤1中我们要选取未访问结点中权值和最小的点,注意是最小值
明显地,我们可以考虑使用最小堆进行维护,维护后时间复杂度为O(nlogn)
#include<iostream>
#include<vector>
#include<cstring>
#include<queue>
using namespace std;
int n,m,u,v,w,st,ed;
struct edge{
int v,w;
bool operator<(edge k)const{//重载<运算符,服务最小堆
return w>k.w;
}
};
vector<edge>g[10001];
int d[10001];
bool vis[10001];
priority_queue<edge> q;//最小堆
void dijkstra(int st){
memset(d,127,sizeof(d));
d[st]=0;
q.push(edge{st,0});
while(!q.empty()){
int u=q.top().v;//获取未确定的最小点
q.pop();
if(vis[u])continue;
for(int i=0;i<g[u].size();i++){//访问u的邻接结点,更新更优路径
int v=g[u][i].v;
int w=g[u][i].w;
if(d[v]>d[u]+w){//松弛
d[v]=d[u]+w;
q.push(edge{v,d[v]});
}
}
vis[u]=1;
}
}
int main(){
cin>>n>>m;//n个点,m条边
for(int i=1;i<=m;i++){
cin>>u>>v>>w;
g[u].push_back(edge{v,w});
}
cin>>st>>ed;//起点和终点
dijkstra(st);
cout<<d[n];
return 0;
}