Dijkstra算法

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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值