dijkstra算法-朴素法与优先队列优化法

算法简述:

        给出一个起点start找出它到其他任意点的最短路径长度。我们需要准备两个集合S和U,S为已经找到最短路径的点的集合,U为尚未找到最短路径的点的集合

1.朴素法(且使用邻接矩阵)

minDist[i]用来储存起点到i节点的最短路径长度,isvisited[i]表示i节点是否已经被访问过了

在有值的minDist中进行遍历,找到目前距离起点最近的节点pos,去访问这个节点更新其他节点的最短路径长度,isvisited[pos]=1,将pos节点转变为已经访问过了

更新方法:遍历所有的节点j,如果graph[pos][j]不等于无限大(也就是pos到j节点有边)并且它没有被访问过 isvisited[j]==0,并且minDist[j]>minDist[pos]+graph[pos][j],那么就更新这个minDist[j]为minDist[pos]+graph[pos][j],即从pos到j比从其他的点到j的路径更短

注意:理解此处访问的含义,一个节点被访问意味着这个节点的最短路径长度已经固定。因为算法的流程为每次先找没有被访问过的最近的点,再通过这个点去更新,如果被访问过的节点v还不是最短路径,一定存在通过别的节点u连接到v,那么在最开始选择的时候不会选择v作为访问点而是u,故被选为访问点的节点最短路径长度一定不会再改变,但其实删去isvisited的判断也可以,因为后面minDist的比较一定可以正常筛选,但这样可以减少判断语句,也更符合逻辑

由题可知,每次循环都可以确认一个访问点,故N次一定可以让所有连通的点都被访问,故最外层由一个N大小的for循环,函数体代码如下

void dijkstra(vector<int> &isvisited,vector<int> &minDist,vector<vector<int>> &graph){
    for(int j=1;j<=N;j++)//遍历所有节点
    {//首先找到现在距离起点最近的切没有被访问过的点
    int MIN=INT_MAX;
    int pos=0;
    for(int i=1;i<=N;i++){
        if(isvisited[i]==0&&minDist[i]<MIN){
            MIN=minDist[i];
            pos=i;
        }
    }
    isvisited[pos]=1;//访问这个点然后更新新的minDist
    for(int i=1;i<=N;i++){
        if(isvisited[i]==1&&graph[pos][i]!=INT_MAX&&minDist[i]>minDist[pos]+graph[pos][i]){
            //此处的isvisit判断可以对以后的两点成环图可以有保障,但其实删去的话也没什么,在后面的条件判断中也会使得算法正常运行
            minDist[i]=minDist[pos]+graph[pos][i];
        }
    }}
}

整题代码如下:

卡码网题目链接:参加科学大会

#include<bits/stdc++.h>
#include <iostream>
using namespace std;
// #define INT_MAX INT_MAX
int N,M;
void dijkstra(vector<int> &isvisited,vector<int> &minDist,vector<vector<int>> &graph){
    for(int j=1;j<=N;j++)//遍历所有节点
    {//首先找到现在距离起点最近的切没有被访问过的点
    int MIN=INT_MAX;
    int pos=0;
    for(int i=1;i<=N;i++){
        if(isvisited[i]==0&&minDist[i]<MIN){
            MIN=minDist[i];
            pos=i;
        }
    }
    isvisited[pos]=1;//访问这个点然后更新新的minDist
    for(int i=1;i<=N;i++){
        if(isvisited[i]==0&&graph[pos][i]!=INT_MAX&&minDist[i]>minDist[pos]+graph[pos][i]){
            //此处的isvisit判断可以对以后的两点成环图可以有保障,但其实删去的话也没什么,在后面的条件判断中也会使得算法正常运行
            minDist[i]=minDist[pos]+graph[pos][i];
        }
    }}
}
int main(){
    //int N,M;
    cin>>N>>M;
    vector<vector<int>> graph(N+1,vector<int> (N+1,INT_MAX));//初始化邻接矩阵
    for(int i=0;i<M;i++){
        int s,e,v;
        cin>>s>>e>>v;
        graph[s][e]=v;
    }
    vector<int> minDist(N+1,INT_MAX);
    vector<int> isvisited(N+1,0);//0表示还没有被访问到,被访问到的意思是以自身作为节点去更新别的节点的最小路径长度
    minDist[1]=0;//起点到自身的最短距离为0
    dijkstra(isvisited,minDist,graph);
    if(minDist[N]==INT_MAX){
        cout<<-1;
    }else{
        cout<<minDist[N];
    }
    return 0;
}

代码随想录链接:朴素dijkstra

2.基于优先队列(小顶堆)的优化方法(且使用邻接表)

具体注意点可以见代码注释中,重点在于对堆的维护,邻接表和minDist的意义时刻把握准确

初次上手难点:

        1.邻接表的遍历使用,邻接表的遍历使用比邻接矩阵更为简介,它只遍历index节点连接的节点,而不需要像邻接矩阵去遍历graph[index][j]所有的j去判断是否有边

        2.优先队列的使用,它不是存边或者节点进去,而是存pair进去,pair分别记录了节点编号index与当时到达index节点的最短路径的值,也就是minDist[index],这样在小顶堆维护时就使用最短路径的值进行维护,并能够返回出当时最短路径的节点编号

        3.优先队列中自定义比较方法;这里选用一种比较好记的方式进行定义。

定义比较类 cmp,这里与sort函数的比较函数区别开来,使用一个结构体或者类进行方式定义,并在该类或者结构体中对operator方法进行重写,eg:bool operator()(int a,int b){return a>b;},这里的cmp返回值可以理解为如果返回为真则前者在队列的前面,后者在队列的后面,而最后面就视为堆的堆顶。

        4.初始化时,即要初始化minDist[1]=0,也要初始化q,将pair<int,int>(1,0)进堆

        5.因为使用堆唯一的变化就是,我们在查找目前最进的点时是直接返回堆顶的节点,那么就要求q中有所有目前有最短路径的值的点,也就是minDist不为INT_MAX的点,所以最开始只有起点在q中,故在对弹出的堆顶的元素而言,它对应有连接的别的节点,我们也需要再遍历它们时把它们形成的pair加入到堆中,下方代码中“q.push(pair<int,int>(x.index,minDist[x.value]));”可以放在if内也可以放在if外,只是相当于用不用在维护堆的时候考虑那些minDist=INT_MAX的节点,实际上没有影响,且复杂度没有变化

#include<bits/stdc++.h>
#include <iostream>
using namespace std;
int N,M;
typedef struct Edge
{
    int index;
    int value;
}Edge;
//自定义对运算符的重载
struct cmp{
    bool operator()(pair<int,int> &a,pair<int,int> &b){
        return a.second>b.second;//小顶堆
    }
};
void dijkstra(priority_queue<pair<int ,int>,vector<pair<int ,int>>,cmp> &q,vector<list<Edge>> &graph,vector<int> &minDist,vector<int> &isvisited){
    while(!q.empty()){//第一步是找到目前距离起点最近的没有被访问过的点,如果堆顶杯被访问过则直接弹出
    int index=q.top().first;int minlen=q.top().second;
    q.pop();
    if(isvisited[index]==1)//被访问过
    {
        continue;
    }
    //访问该点
    isvisited[index]=1;
    //第三步要更新最近的路径了
    for(auto x:graph[index]){
        //遍历index节点可以到达的点x,或者说边
        if(isvisited[x.index]==0&&minDist[x.index]>minDist[index]+x.value){
            minDist[x.index]=minDist[index]+x.value;
            q.push(pair<int,int>(x.index,minDist[x.value]));//每次去更新minDist时其实无论更没更新成功都可以将"新"的minDist[i]与i进堆进行维护,因为一开始只有起点自己是有0的最短路径所以只入堆了起点,但后面开始从index开始遍历index连接的节点时,这些节点也要入堆这样后面小顶堆返回的值才是整个图中最短路径的节点
        }//q.push(pair<int,int>(x.index,minDist[x.value]));均可
    }}
}
int main(){
    cin>>N>>M;
    vector<list<Edge>> graph(N+1);//使用邻接表进行储存
    for(int i=0;i<M;i++){
        int s,e,v;
        cin>>s>>e>>v;
        Edge edge;
        edge.index=e;edge.value=v;
        graph[s].push_back(edge);
    }
    //这里要清楚优先队列存的什么,因为优先队列是对每次遍历n的顺序查找的优化,变成有些队列直接返回那个离起点最近的没被访问过的点,所以作为小顶堆存的是minDist的值以及节点编号,故不能存Edge进去,使用pair
    priority_queue<pair<int ,int>,vector<pair<int ,int>>,cmp> q;//first存节点编号,second存目前距离起点的最小值
    vector<int> minDist(N+1,INT_MAX);
    vector<int> isvisited(N+1,0);
    //初始化,注意这里需要同时初始化优先队列和minDist数组
    minDist[1]=0;
    q.push(pair<int,int>(1,0));
    //q.push()
    dijkstra(q,graph,minDist,isvisited);
    if(minDist[N]==INT_MAX){
        cout<<-1;
    }else{
        cout<<minDist[N];
    }
    return 0;
}

基于优先队列的dijkstra算法

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值