Dijkstra算法_最短路算法
\(Dij\)是一类基于贪心的最短路算法。
首先我们明确这个算法是干什么的,这个算法是用来跑单源最短路的,也就是给你一张图,给出某一个特定的起点,你需要求出这个点到其他任意点的最短路。
其次,我们明确它可以解决什么样的问题,它可以解决单源最短路问题,实际上,对于给一个起点问到其他点最短路的问题,它是未必能够解决的。
它是用于跑单源最短路的一个算法,不过,算法的正确性是基于只存在非负边权,如果存在负数边权,那么它就无法保证正确性了。
上面已经知道了它的用途,那么接下来我们讲一讲如何实现。
初始化
首先我们初始化,记录一个\(dis[]\)数组,\(dis[i]\)表示给定的起点\(s\)到\(i\)的最短距离,那么一开始都设成正无穷,意思是这些点目前都无法到达,然后,我们把\(dis[s]\)设为\(0\),含义是自己到自己是\(0\),然后我们把\(s\)这个点放入更新集合,到这里,我们就完成了\(Dij\)算法的初始化。\(dis[]\)数组,也正是我们要求出来的答案数组。
Code
memset(dis,0x3f,sizeof(dis));//初始化为正无穷
priority_queue <node> Q;//开一个更新集合,为啥用优先队列会在下文讲解
Q.push((node){0,s});//把s放进去 在更新集合里面的点可以往外拓展,进行更新
dis[s]=0;//初始化自身dis
算法过程
从更新集合中取出\(dis\)最小的,然后让他扩展,扫所有能到达的点\(to\),如果\(dis[to]>dis[更新点]+边权\),那么我们就把\(dis[to]\)塞到更新集合里,并更新它的\(dis\)。
用优先队列,就是为了优化取出这个\(dis\)最小的位置,为什么不把没有更新的塞到集合里?因为如果此时它不能被更新,是不是说明这个点的\(dis\)一定曾经被更新过,不再是正无穷了?那么因为他被更新过,它是不是被放进过更新集合?也就是它被更新过的那个\(dis\),一定曾经在集合里,那么就不用放进去了,否则就重复了。
正确性?
为什么我们\(Dij\)取出来之后就打标记不再取了?这是因为我们知道,必然接下来取出来的是曾经被放进去的这个点,必然是那些不优解,什么?你觉得可能有更优解?不可能的,注意到如果所有的边都是正权边,我们每一次放进去的点的\(dis\)都是单调递增的,从而不可能一个点被取出来之后重新放进去更优,如果在他之前放进去,那肯定比它还先取出来,从而我们证明了,取出来一遍就可以打标记永远不取了。
那么怎么保证一定会取到最优解?这是因为我们应用了贪心的思想,每次都用最优点更新,但凡有更优解,一定会在某一个时刻优于这个不优的状态,从而被先取出来。
第一个正确性保证了时间复杂度,第二个正确性保证了算法的正确性。
\(summary\)
人生中贪心不一定是好事,但算法中的贪心,更多的是朝着更优的情况努力的贪心,是一种拼搏进取向上的贪心,该为更好自己"贪心"的时候就应该贪心。
\(Dij\)的思想也是贪心,但人生没有最短路,每一条道路,都是最完美的安排,每一个人,都是最完美的人。
完整\(Code\)
#include<bits/stdc++.h>
using namespace std;
const int N=100086,M=500086;
int n,m;
int cnt=-1,dis[N],vis[N],s;
int head[N];
struct Edge{
int nxt,to,v;
}e[M];
struct Node{
int dis,id;
bool operator < (const Node &t)const{
return dis>t.dis;///堆默认大根,所以大于号定义小于号则有小根堆
}
};
priority_queue<Node> Q;
void add_edge(int x,int y,int v){
e[++cnt]=(Edge){head[x],y,v};///cnt初始值-1,边0--(m-1) 下一边为原链表头,记录到谁,边权
head[x]=cnt;///覆盖原链表头,成为头
}
void Dij(){
memset(dis,0x3f,sizeof(dis));///初始化
memset(vis,0,sizeof(vis));///初始化
Q.push((Node){0,s});///初始化
dis[s]=0;///初始化
for(;!Q.empty();){
Node t=Q.top();
Q.pop();
int id=t.id;
if(vis[id])continue;
vis[id]=1;
for(int p=head[id];~p;p=e[p].nxt){
int y=e[p].to,v=e[p].v;
if(dis[y]>dis[id]+v){
dis[y]=dis[id]+v;
Q.push((Node){dis[y],y});
}
}
}
}
int main(){
scanf("%d%d%d",&n,&m,&s);///点数 边数 起点
memset(head,-1,sizeof(head));///链表初始-1
for(int i=1,x,y,v;i<=m;i++){
scanf("%d%d%d",&x,&y,&v);
add_edge(x,y,v);
///add_edge(y,x,v); 双向边
}
Dij();
for(int i=1;i<=n;i++){
if(dis[i]==0x3f3f3f3f){
printf("NO PATH\n");
}
else printf("%d\n",dis[i]);
}
return 0;
}