单源最短路径--Dijkstra

Dijkstra的用途:

  • Dijkstra是一个求单源最短路径的算法。

  • "单源最短路径",顾名思义,从一个源头到其他结点的最短路径。而这个算法,可以求出单个点对其他所有点的最短路径长度。(有些情况是Dijkstra不能处理的,比如负边权,遇到这类情况可能就需要使用其他算法了)

 
Dijkstra的思想:

  • 主要思想:

  • 从源结点开始,遍历源结点的所有连接的边,并进行排序,再从边的值最小的一个边开始遍历,在每一次遍历的过程中,都尝试更新所储存的某个结点对于源结点的最短路径长度(假设我们要到点B,目前我们遍历到了点A,如果点A到点B的距离加上点A到源点的距离比目前我们之前存储的 源点到点B的距离短的话,就更新源点到点B的最短路径,并在之后遍历连着点B的点(也就是进行BFS))。

  • 例子:

  • 首先,从源结点开始进行BFS(也就是遍历到达的点所向外连着的点.比如A->B B->A A->C(A连B,B连A,A连C),那么BFS A结点就是B C)

  • 在BFS的过程中,每到一个点,就比较一下从 源头到这个点的路径长度 和 之前求的从源头到这个点的最短路径的长度 大小,如果现在这个路径长度比较小,就存起来

Dijkstra的实现方法:

  • 首先,存图(使用邻接表或链式前向星(如果不会存图建议先学存图,因为这里不会讲怎么存图和BFS)).
  • 算法运行过程:
  1. 把数据(也就是dis数组)初始化为INF(为什么要这样做?这是因为我们要使用较小的值不停更新它,这样最终就是源点到这个点的最小值了。那怎么知道某个点所对应的与源点的距离呢?一般是把源点的序号当做数组的下标。下标是什么意思呢?比如使用这个代码:int i=A[10],10就是数组A的一个下标。源点的序号是什么意思呢?一般使用源点读入的顺序当做源点的序号)
  2. 准备一个优先队列,用于之后进行的BFS.
  3. 在BFS的过程中不断更新最小值,并且,如果一个点被更新了,就再把被更新的点推入优先队列,准备之后在这个点上进行BFS.
  4. 优化

Dijkstra的优化:

  • 优化方法:
  • 堆优化(这就是使用优先队列(这里我的代码使用了STL中的priority_queue)的原因)。因为我们是从源点开始遍历,并不停用离源点尽量小的点来更新其他点的当前最短路径,所以如果遍历队列中的结点时,发现这个结点中所存储的 向队列推入这个点时,这个点离源点的距离 与当前这个点到源点的最短路径不同的话,这是因为这个点的最短路径已经被别人更新过了,也就是说这个点的数据已经无法使用了。并且,在更新这个点的最短路径(在我的实现中使用了dis数组(这个括号内的内容并不重要,只是为了防止一些人不明白怎么更新这个点的最短路径))时,肯定已经把这个点推到队列中了。因此可以得出:如果这个点中储存的最短路径(我们用Node结构体,存储点的序号和将这个点推入队列时源点到这个点的最短路径)和目前的最短路径不一样,那么必然这个点中存储的最短路径是较长的,并且这个点已经无用了,只会造成重复计算 所以,当我们判断到这个条件时,就可以直接跳过这个结点。

Dijkstra的使用注意事项:

  • 如果图是无向图,需要双向加边(也就是调用两次addEdge方法,比如A-B这条边 就可以addEdge(a,b);addEdge(b,a)(这里忽略了权值))

模板题目地址:https://www.luogu.org/problemnew/show/P4779

重载运算符版:

#include
#include
#include
#include
using namespace std;
int s,cnt=0,dis[1000010],head[1000010];
//s:源结点 cnt:边总数,用于存边 dis:存储当前的源结点到某个结点的最短路径长度(下标是结点序号) head:邻接表, 
struct Edge{
    int v,w,nxt;
}e[500010];
struct Node{
    int u,d;
    bool operator <(const Node& rhs)const{
        return d>rhs.d;//重载运算符,这个需要背过 
    }
};
void addEdge(int u,int v,int w){
    e[++cnt].v=v;
    e[cnt].w=w;
    e[cnt].nxt=head[u];
    //关于邻接表: 
    //首先要明确,邻接表是一个链式结构,也就是说,首先添加的边在邻接表的最后面
    //最后添加的在前面,在遍历的时候也是从头往后遍历,也就是说最先添加的反而最后被遍历到. 
    //当遍历到最后一个的时候 由于最后一个的后面已经没有结点了,那么此时i的值就成为了0(具体看下面的for循环代码)
    //成为0了,也就代表着循环该结束了.这时候,循环条件不成立,循环就结束了. 
    head[u]=cnt;
}
void dijkstra(){
    dis[s]=0;
    priority_queueq;
    q.push((Node){s,0});
    while(!q.empty()){
        Node now=q.top();q.pop();
        int d=now.d,u=now.u;
        if(d!=dis[u])continue;//堆优化,等同于 if(d>dis[u])continue;,可以相对较好地减少时间复杂度
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].v,w=e[i].w;
            if(dis[v]>dis[u]+w){
                dis[v]=dis[u]+w;
                q.push((Node){v,dis[v]});
            }
        }
    }
}
int main(){
    int n,m;
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1;i<=n;i++)dis[i]=2147483647;//将源结点到其他结点的路径长度初始化为尽可能大的值,也就是INF 
    for(int i=1;i<=m;i++){
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        addEdge(x,y,z);
    }
    dijkstra();
    for(int i=1;i<=n;i++)printf("%d ",dis[i]);
    return 0;
}

 

Pair版:

#include
#include
#define P pair
#define INF 2147483647
#define maxn 500005
using namespace std;
priority_queue,greater

>q; int dis[maxn],vis[maxn],head[maxn]; int n,m,s; int cnt; struct Edge{     int u,v,w,next; }e[maxn*2]; void add(int u,int v ,int w){     e[++cnt].u=u;     e[cnt].v=v;     e[cnt].w=w;     e[cnt].next=head[u];     head[u]=cnt; }  void dijkstra(){     while(!q.empty()){         int x=q.top().second;         q.pop();         if(!vis[x]){             vis[x]=1;             for(int i=head[x];i;i=e[i].next){                 int v=e[i].v;                 dis[v]=min(dis[v],dis[x]+e[i].w);                 q.push(make_pair(dis[v],v));             }         }              }      } int main(){     cin>>n>>m>>s;     for(int i=1;i<=m;i++){         dis[i]=INF;     }     dis[s]=0;     q.push(make_pair(0,s));     for(int i=1;i<=m;i++){         int a,b,c;         cin>>a>>b>>c;         add(a,b,c);      }     dijkstra();     for(int i=1;i<=n;i++){         cout<

 

你可能感兴趣的:(单源最短路径--Dijkstra)