Dijkstra最短路径寻找算法,本质其实就是一个贪心的算法,
接下来就介绍两个Dijkstra算法的用法,一个用于稠密图,另外一个用稀疏图。
朴素版——用于稠密图
稠密图,意思就是会有很多点形成的有向图,需要找到从源点到达结尾的最短路径
那么若想找到到达最后一个位置的最短距离,那么就是需要获得到达n-1的点的最短距离,然后到达n点的最短距离就是在所有可以到达 n点的abcd点中 ,取min(到达abcd点的最小值+abcd分别到达n点的距离的和),那么就是到达n点的最短距离
因此需要用一个数组dist[i] ,来存储从源点开始,到达i点的所有路径中的最小值。
那么应该用什么来存储a点可以到达b点的信息,并且由a到b这条边的权值呢??
用二维数组g[i][j] 存储,表达 i点的下一个点就是j点,且两点之间的距离是 g[i][j]
该怎么更新某个点的最小距离呢??
首先就是要建树,因为每一个点的最小距离都是由前边的点得到的
因此需要按照源点的顺序来依次遍历,将把与该点连接的下一个点进行更新。
在这个过程中,邻接点的距离也会被更新,但是会因为已经确定了最小值,所以不会重复遍历
所以这其实是一个不断刷新的过程,在源点开始往后遍历的过程中,途径的点的最小距离都会被不断更新。
所以用一个数组st[N] 来存储已经确定的最小距离的值。
初始化
由于要获得的是到达每个点的最小的距离,所以要先把g[i][j] 和dist 中的值初始化成最大值,这样不仅在获取最小值的同时,还可以以此作为判断,若到达dist[n] 的值==最大值,说明到达不了n点,可以直接删除-1
代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=510;
int dist[N]; //存储从源点到j点的最短距离
int g[N][N];//用来表示i可以到达j,边权值
bool st[N];//用来标记i点是否已经得到了最大值
int n,m;
int dijkstra(){
memset(dist,0x3f,sizeof dist);//全都初始化为0x3f,表示不能到达此处
dist[1]=0;//1到1的距离就是0
for(int i=0;i<n-1;i++){
int t=-1; //搜索当前没有确定的最短路的长度的点中,距离原点最小的那一个,其实就是根据顺序来遍历
for(int j=1;j<=n;j++){
if(!st[j]&&(t==-1||dist[j]<dist[t])){ //j必须是没有确定最短的路径,而且t==-1的时候就可以直放入
//此处就是按照连接的顺序去依次往后,t就是获取以t为开头,往后连接的节点
//在不同情况下的连接顺序不一定是12345这样连接的
t=j;
}
}
for(int j=1;j<=n;j++){
dist[j]=min(dist[j],dist[t]+g[t][j]);
//所以此处到j的最小值,可能是从源点到t+t到j的距离小于源点直接到j的最小距离,反正就是获取到j的最小距离。
}
st[t]=true; //已经获取了到t的最小距离
}
if(dist[n]==0x3f3f3f3f) return -1;
return dist[n];
}
int main(){
cin>>n>>m;
memset(g,0x3f,sizeof g);
for(int i=0;i<m;i++){
int x,y,c;
cin>>x>>y>>c;
g[x][y]=min(g[x][y],c);
}
cout<<dijkstra()<<endl;
return 0;
}
优化版——用于稀疏图
对于一些点较少的,就不需要一个个去遍历获取元素了,只需要获取此处的点连接的下一个点,而不是像稠密图那样有许多点的,稠密图就是每个点都会读取遍历,但是到达不了的点,距离就是正无穷,但是因为取的是min,所以不会有很多影响。
在稀疏图中,首先就是要建立一个有向图,并且把边赋予权值,此处就用链式向前星,在建立边的时候要把权值赋予进去
int h[N],e[M],he[M],w[M],idx;
void add(int a,int b,int c){
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
赋予权值后,那该如何每次都获取没有确定最短路径长度的点中,距离最小的那一个呢??
用一个小堆栈来获取,通过每个编号的最短路径来进行排序,那么堆顶获取的就是该编号的最短路径
但是为了防止重复读取,因为在获取点的时候,可能会更新邻接点的最短距离,因此需要一个st数组来防止重复读取
思路
堆优化版的dijkstra是对朴素版dijkstra进行了优化,在朴素版dijkstra中时间复杂度最高的寻找距离
最短的点O(n^2)可以使用最小堆优化。
1. 一号点的距离初始化为零,其他点初始化成无穷大。
2. 将一号点放入堆中。
3. 不断循环,直到堆空。每一次循环中执行的操作为:
弹出堆顶(与朴素版diijkstra找到S外距离最短的点相同,并标记该点的最短路径已经确定)。
用该点更新临界点的距离,若更新成功就加入到堆中。
代码
#include <iostream>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=2*1e5;
const int M=2*N;
int h[N],e[M],ne[M],w[M],idx;
int dist[N];//标记距离
bool st[N];//标记是否已经读取了
typedef long long ll;
typedef pair<int,int> PII;
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(dist,0x3f,sizeof dist);//初始化为最大值
priority_queue<PII,vector<PII>,greater<PII>> q;//小堆的初始化
q.push({0,1});//分为路径+编号的形式
dist[1]=0;
while(q.size()){
auto k=q.top();
q.pop();
int ver=k.second,distance=k.first;//ver代表编号,distance代表到达该点的最小距离
if(st[ver]) continue;//如果当前点读取过了,那么就不用管了
st[ver]=true;//标记读取过了
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];
q.push({dist[j],j}); //若遍历到j点的距离为 5 4 3 2 1 ,会有五个,
//但是由于小堆的特点,优先读取的会是距离1,因此从j点往后的距离,都会是其到达j点的最小值1往后遍历更新
}
}
}
if(dist[n]==0x3f3f3f3f) return -1;
return dist[n];
}
int main(){
cin>>n>>m;
memset(h,-1,sizeof h);
for(int i=0;i<m;i++){
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
cout<<dijkstra()<<endl;
return 0;
}