Dijkstra算法求最短路问题

本文探讨了在稠密图和稀疏图背景下,如何利用堆优化技术改进Dijkstra算法,以降低时间复杂度。通过实例和代码展示了朴素版与堆优化版Dijkstra算法的区别,重点讲解了堆优化如何处理负权边和最短路径问题。
摘要由CSDN通过智能技术生成

地址:

描述:

稠密图:

 用堆优化(稀疏图):

思路:

堆优化:

 代码:

朴素版

堆优化:


地址:

https://www.acwing.com/problem/content/851/

描述:

m是n^2级别的话就是稠密图,m是n级别的就是稀疏图

稠密图:

 用堆优化(稀疏图):

思路:

为什么不能处理有负权边的最短路?

  1. 初始时, S只包含起点s;U包含除s之外的其他顶点,且U中顶点的距离为“起点s到该顶点的距离”【例如:U中顶点v的距离为(s, v)的长度,然后s和v不相邻,则v的距离为∞】。
  2. 从U中选出“距离最短的顶点k”,并将顶点k加入到S中;同时,从U中移除顶点k。
  3. 更新U中各个顶点到起点s的距离。之所以更新U中顶点的距离,是由于上一步中确定了k是求出最短路径的顶点,从而可以利用k来更新其他顶点的距离;例如,(s, v)的距离可能大于(s, k)+(k, v)的距离。
  4. 重复步骤2和3,直到遍历完所有顶点。
     

98504cdcddd6d683a91c09357395d12.png

55fec2d8dd5e91cdc6f8b5d179a7d19.png

堆优化:

堆优化版的dijkstra是对朴素版dijkstra进行了优化,在朴素版dijkstra中时间复杂度最高的寻找距离最短的点O(n^2)可以使用最小堆优化。
1. 一号点的距离初始化为零,其他点初始化成无穷大。
2. 将一号点放入堆中。
3. 不断循环,直到堆空。每一次循环中执行的操作为:
    弹出堆顶(与朴素版diijkstra找到S外距离最短的点相同,并标记该点的最短路径已经确定)。
    用该点更新临界点的距离,若更新成功就加入到堆中。

eac97a72da924b845002f98e1c727f3.png

 代码:

朴素版

#include <iostream>
#include <cstring>
using namespace std;
const int N=510;
#define INF 0x3f3f3f3f
//用来存放节点间距离
int g[N][N];
//确定好与起点最短距离的节点
int stu[N];
//节点与起点之间距离
int dist[N];
int n,m;
int Dijkstra(){
    //起点与起点的距离为0
    dist[1]=0;
    //迭代n次,每次可以确定一个点到起点的最短路
    for(int i=0;i<n;i++){
        //t的作用判断有没有更新过
        int t=-1;
    //从1~n号节点中找,不在s集合,并且
    //没有更新过(t==-1),则进行更新, 或者发现更短的路径(dist[t]>dist[j]),则进行更新
        for(int j=1;j<=n;j++){
           if(!stu[j]&&(t==-1||dist[t]>dist[j])) {
               t=j;
           }//if
        }//for(j)
        stu[t]=true;
//找到了距离最小的点t,并用最小的点t去更新1~n号节点到起点的距离
//这里可能有同学要问j如果从1开始的话 会不会影响之前已经确定的点的最小距离
//但其实是不会 因为按照我们的Dijkstra算法的操作顺序 
//先确定最短距离的点的距离已经比后确定的要小 所以不会影响
//当然你也可以在循环判断条件里加上if(!st[i])
//这里j从1开始只是为了代码的简洁
        for(int j=1;j<=n;j++){
            dist[j]=min(dist[j],dist[t]+g[t][j]);
        }//for(j)
    }//for(i)
    //说明没有出路
    if(dist[n]==0x3f3f3f3f) return -1;
    else return dist[n];
}
int main(){
    cin>>n>>m;
    //初始化距离
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(i==j) g[i][j]=0;
            else g[i][j]=INF;
        }
    }
    //初始化dist[N]为无穷大
    memset(dist,0x3f,sizeof dist);
    for(int i=0;i<m;i++){
        //m条边,但有重边,所以当节点间有重边时,取边长最小的一条边
        int a,b,c;
        cin>>a>>b>>c;
        //取边长最短的一条边
        g[a][b]=min(g[a][b],c);
    }
    cout<<Dijkstra();
    return 0;
}

堆优化:

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
typedef pair<int ,int >PII;
const int N=150010;
int n,m;
//稀疏表用邻接表存储,w[N]存储的是权重
int h[N],e[N],ne[N],w[N],idx;
int dist[N];
bool stu[N];
void add(int a,int b,int c){
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int Dijkstra(){
    //定义一个小根堆
    priority_queue<PII,vector<PII>,greater<PII>> heap;
    dist[1]=0;
    heap.push({0,1});
    while(!heap.empty()){
        //这里不能写heap.front,没有这个函数
        auto temp=heap.top();
        heap.pop();
        //distance是起点到temp的距离,ver是temp节点的编号
        int distance=temp.first,ver=temp.second;

      //dist[5]=9(在堆中{9,5},第一次更新时加入),dist[5]=7(在堆中{7,5},第二次更新时加入)
      //使用时用的是dist[5]=7,将该点({7,5})弹出后,在下一次循环中,如果{9,5}在堆顶的话,使用时
      //两者间肯定要选距离要小的那个,不能使用{9,5}重复更新,所以要用st数组进行标记
        if(stu[ver]) continue;
        //找到距离最近的节点加入小根堆
        stu[ver]=true;
        //通过temp节点更新距离
        //i存储与temp节点相连通的节点的编号,所以w[i]存储的就是temp节点到j节点边的权重
        for(int i=h[ver];i!=-1;i=ne[i]){
            int j=e[i];
            //distance 存储从1~temp的距离,w[i]存储从temp~j的距离
            if(dist[j]>w[i]+distance){
                dist[j]=w[i]+distance;
                heap.push({dist[j],j});
            }
               //更新距离之后将该点的距离加入到堆中,这也是上述为何要进行标记的原因,
               //因为一个点的距离加入堆的次数可能有两次甚至更多,这样会影响到其他的点
                //例如:
                //{9,5},{7,5},{10,6},如果{7,5}被弹出后,堆中剩余的是{9,5},{10,6},堆顶
                //的元素是{9,5}而5这个点的距离已经被使用过了,所以要将{9,5}这个点忽视掉
        }
        
    }
    if(dist[n]==0x3f3f3f3f) return -1;
    else return dist[n];
}
int main(){
    cin>>n>>m;
    
    //初始化dist[N]为无穷大
    memset(dist,0x3f,sizeof dist);
    memset(h,-1,sizeof h);
    for(int i=0;i<m;i++){
        //m条边,但有重边,所以当节点间有重边时,取边长最小的一条边
        int a,b,c;
        cin>>a>>b>>c;
       add(a,b,c);
    }
    cout<<Dijkstra();
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值