目录
dijkstra算法求最短距离步骤
朴素的dijkstra算法---acwing-849
代码如下
代码思路
堆优化版的dijkstra算法---acwing-850
代码如下
关于最短路问题分有好几种类型 :
单源就是指:只求从一个顶点到其他各顶点
多源是指:要求每个顶点到其他各顶点
这些情况对应有不同的算法,这次先介绍dijkstra算法的两种。
dijkstra算法求最短距离步骤
我们手写的步骤就是:
1.
确定我们要处理的单源点。即我们是想 从哪个顶点开始寻找到其他顶点的最短路径。
2.
第一轮:看该顶点到其他任意顶点的距离并记录下来。一般只有直连的顶点是边的权重,其他不直连的都是正无穷,每个顶点都看过之后,选出这些记录下来的距离的最小值所对应的顶点编号。
3.
相当于第二轮的时候,我们可以看作,起始顶点是上一轮选出的最小距离的顶点,且起步价就是初始值是这个最小距离。由此不断迭代直到寻找过所有顶点到开始顶点的最短距离。【我觉得这种思路比较好理解,对应下面视频讲解的第二个】
如下图 (INF表示的是正无穷)
关于该算法的一些视频讲解的推荐:
【图-最短路径-Dijkstra(迪杰斯特拉)算法】
【【全网第二清晰】手写迪杰斯特拉-Dijkstra(考试用)】
讲的都很清晰,可以看一下
朴素的dijkstra算法---acwing-849
代码如下
步骤明白之后代码的含义需要解释的都写在注释里了。
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=510;
int n,m;
int g[N][N];//用邻接矩阵来存储稠密图,二维数组的每一位表示【行标指向列标的边 所对应的权重】
int dist[N];//存储从起点到其他节点当前的最短距离
bool st[N];//标记该点是否已经已经被寻找过int dijkstra()
{memset(dist,0x3f,sizeof dist);//初始状态所有的距离都是正无穷dist[1]=0;//习惯将节点从1开始编号,且本题求的就是1节点到其他节点的最短距离。该点到其自身的距离为0//确保了寻找了每个节点到源节点的最短路径for(int i=0;i<n;i++){int t=-1;// 用于标记当前轮次中【距离最短的未访问节点的编号】,初始化为 -1 表示尚未找到有效节点for(int j=1;j<=n;j++){if(!st[j] && (t==-1 || dist[t]>dist[j])){t=j;//找到当前轮次中距离最短的未访问节点的编号}}st[t]=1;//找到后将其标记为1//通过选定的节点更新到其他节点的最短距离for(int j=1;j<=n;j++){dist[j]=min(dist[j],dist[t]+g[t][j]);//g[t][j]表示经过节点t到达节点j的路径长度}}if(dist[n]==0x3f3f3f3f)return -1;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 t=dijkstra();cout<<t;return 0;
}
代码思路
1.根据题目中的数据范围,边数远远大于顶点数,因此是稠密图,且用邻接矩阵存储图。 而这里每条边都会有各自权值,因此用邻接矩阵加边直接更改二维数组中对应位置为对应权重即代表了存在权值为x的边。不用单独写添加边的函数。
2.求1->n任意一个顶点的最短距离,使用dijkstra算法。单独分装函数,函数内部也就是代码实现该算法的步骤。
3.dijkstra函数内部。首先初始化dist数组值为正无穷。注意dist数组的含义是:下标表示顶点,该数组表示下标对应的顶点距起始顶点的最短距离。由于我们是从1号顶点开始,且其自身到自身的距离应该是0,由此初始dist[1]=0 ;
之后就开始经过n轮寻找,确保每一个顶点都进行了寻找,因此外层循环次数为n次。由于只是控制次数,所以从0~n-1的循环条件是可以的,当然也可以写1~n
需要创建一个st数组做标记,标记那些已经找到最短距离的点。下标表示点的编号。
每次寻找需要做什么?观察所有顶点(内层for循环),找到距离源点距离最短的顶点,标记该顶点已经找到最短距离,之后遍历所有顶点,更新最短距离,看加了这个点之后其他有没有哪个点有更优的最短距离,进行更改即可。
这段代码的时间复杂度最差是O(n^2),与边数m无关,因此适用于稠密图。
堆优化版的dijkstra算法---acwing-850
这道题的区别就是n和m的取值范围是相同的,都是1.5e5 ,该图为稀疏图。首先不同的就是这道题会采用邻接表存储图。
代码上的不同主要就是:利用堆来简化循环的嵌套,降低时间复杂度。
之前写的关于堆的文章,可以回顾一下。
手写堆
【第十五课】数据结构:堆 (“堆”的介绍+主要操作 / acwing-838堆排序 / 时间复杂度的分析 / c++代码 )
【第十五课】数据结构:堆(acwing-839模拟堆 / ph和hp数组的映射关系 /c++代码 )
stl优先队列
【第十七课】c++常用的STL容器
代码如下
我们这里使用优先队列实现。
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>//使用优先队列
using namespace std;
typedef pair<int,int> PII;//第一个元素表示最短距离,第二个元素表示顶点编号
const int N=1.5e5+10;
int n,m;
int h[N],e[N],ne[N],w[N],idx;
int dist[N];//存储从起点到其他节点当前的最短距离
bool st[N];//标记该点是否已经被处理过void add(int a,int b,int c)
{e[idx]=b;ne[idx]=h[a];w[idx]=c;h[a]=idx++;
}
int dijkstra()
{memset(dist,0x3f,sizeof dist);//初始状态所有的距离都是正无穷dist[1]=0;//习惯将节点从1开始编号,且本题求的就是1节点到其他节点的最短距离。该点到其自身的距离为0priority_queue<PII,vector<PII>,greater<PII>> he;//创建小根堆he.push({0,1});//先将源点加入队列while(he.size())//当队列不为空{PII t=he.top();he.pop();//将当前的最短距离出队int ver=t.second,distance=t.first;//ver表示顶点 distance表示最短距离if(st[ver])continue;//如果这个顶点已经处理过就直接跳过for(int i=h[ver];i!=-1;i=ne[i])//遍历这个点的所有直连的边{int j=e[i];if(dist[j]>distance+w[i])//更新与其直连的点的最短距离{dist[j]=distance+w[i];he.push({dist[j],j});}}}if(dist[n]==0x3f3f3f3f)return -1;return dist[n];
}
int main()
{cin>>n>>m;memset(h,-1,sizeof h);while(m--){int a,b,c;cin>>a>>b>>c;add(a,b,c);}int t=dijkstra();cout<<t;return 0;
}
注意这里其实并没有处理重边,这是因为:我们是按照逐个顶点遍历其所连的边处理的,对于重边我们会和该顶点的其他边一样,进行平等的判断,得出一个最短距离的边。只有最短的边会在当前阶段影响最短路径。
使用优先队列所优化的是求最短距离这一步,本来需要遍历所有节点,找到离源点距离最小的点,时间复杂度为 O(n),通过优先队列实现小根堆,堆顶是最小值,直接将这一步优化为O(1)
感觉堆优化版的更好理解一点,,朴素的做法每层循环的功能要搞明白,清楚执行的什么操作。
好啦先写到这里。
有问题欢迎指出,一起加油!!