dijkstra算法

本文详细介绍了Dijkstra算法,包括其基本原理、代码实现和优化版本,并展示了如何应用于求解最短路径问题。此外,还探讨了算法的变通,如反向建边、超级源点和多权边问题。同时,提到了邻接矩阵和邻接表在空间效率上的考量。文章以实例解析,帮助读者深入理解Dijkstra算法。
摘要由CSDN通过智能技术生成

dijkstra算法的基本总结

我自己其实也没做特别多的题目(也就十道左右,不敢说,了解的很深)
不过,这个算法确实很牛叉
在下都服了,特别是刚学完就去做题,发现只能做新手村的最短路,后面研究了好一会,才会做一下不是新手村 的题,感觉智商不够用,升天吧!!!!

本人还没看spfa,floyd算法,一直在搞dijkstra算法,难过


开始吧

  • dijkstra本人(挺好看滴 0.0)
    在这里插入图片描述

  • 什么是dijkstra算法?
    就是求最短路的一种贪心策略
    通过这个算法来得到我们要的最短路径

  • dijkstra算法的原理
    我简单的说一下,其实这个方法很简单。
    代码也很简单,思维真心简单,网上讲的是天花乱坠,代码也是,各有特色。
    (还是y总代码厉害,有简单,有明白)
    在这里插入图片描述

其实也挺简单的,就是从起始点出发,将其他点变成无穷,然后讲起始点能接触的点,延伸去其他点,每次取最短的距离,以此类推

简单来说就是说,谁短延伸谁,直到找到需要的点,且此时一定是最短的点,如果你们不行,可以去网上找证明,反正我是不会证的,别问我

例如 图示 1 和6,4,3接触,就更新6,4,3的距离,然后3本身距离加6 的距离明显短1到3,所有更新1到6的距离为6,原本为10。以此类推

好了,废话不多说。
这里有两种版本的dijkstra算法,一个针对多边,另一个针对普遍的最短路。
dijkstra算法只针对单源最短路问题
核心代码(模板)
朴素版

int g[N][N];  // 存储每条边
int dist[N];  // 存储1号点到每个点的最短距离
bool st[N];   // 存储每个点的最短路是否已经确定

// 求1号点到n号点的最短路,如果不存在则返回-1
int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[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[t] > dist[j]))
                t = j;

        // 用t更新其他点的距离
        for (int j = 1; j <= n; j ++ )
            dist[j] = min(dist[j], dist[t] + g[t][j]);

        st[t] = true;
    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

堆优化的版本 (我觉得这个版本用的比较多,毕竟很多题都是稀梳图)

typedef pair<int, int> PII;

int n;      // 点的数量
int h[N], w[N], e[N], ne[N], idx;       // 邻接表存储所有边
int dist[N];        // 存储所有点到1号点的距离
bool st[N];     // 存储每个点的最短距离是否已确定

// 求1号点到n号点的最短距离,如果不存在,则返回-1
int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, 1});      // first存储距离,second存储节点编号

    while (heap.size())
    {
        auto t = heap.top();
        heap.pop();

        int ver = t.second, distance = t.first;

        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];
                heap.push({dist[j], j});
            }
        }
    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

针对不同情况我们用不同的模板

其实模板真不重要,重要的是思想,你明白其中的过程和思维,就连背篼不需要背

先来模板题,看看吧
最短路模板题

这里我们就是用dijkstra算法求起点到所有点的最短距离

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
typedef pair<int,int> PII;
const int N=1e5+10,M=2e5+10;
int n,m,s;
int h[N],e[M],ne[M],w[M],idx; //邻接表
int d[N];
bool st[N];
void add(int a,int b,int c){
    e[idx]=b;w[idx]=c;ne[idx]=h[a];h[a]=idx++;
}
void dijkstra(){
    memset(d,0x3f,sizeof d);
    d[s]=0;
    priority_queue<PII,vector<PII>,greater<PII> > heap;
    heap.push({d[s],s});
    while(!heap.empty()){
        auto t=heap.top();
        heap.pop();
        int u=t.second,v=t.first;
        if(st[u]) continue;
        st[u]=true;
        for(int i=h[u];i!=-1;i=ne[i]){
            int j=e[i];
            if(d[j]>d[u]+w[i]){
                d[j]=d[u]+w[i];
                heap.push({d[j],j});
            }
        }
    }
}
int main(){
    cin>>n>>m>>s;
    memset(h,-1,sizeof h);
    for(;m;m--){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
    }
    dijkstra();
    for(int i=1;i<=n;i++) printf("%d ",d[i]==0x3f3f3f3f?0x7fffffff:d[i]);
    return 0;
}

除了求最短路,还可以求最长路
最小花费
这里就是求乘积最大,来求答案

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=2010;
double g[N][N]; //邻接矩阵
int n,m,l,r;
double d[N];
bool st[N];
double dijkstra(){
    //memset(d,0,sizeof d);
    d[l]=1;
    for(int i=0;i<n-1;i++){
        int t= -1;
        for(int j=1;j<=n;j++)
            if(!st[j]&&(t==-1||d[t]<d[j]))
                t=j;
        for(int j=1;j<=n;j++)
            d[j]=max(d[j],d[t]*g[t][j]);
        st[t]=true;
        if(t==r) break;
    }
    return d[r];
}
int main(){
    cin>>n>>m;
    //memset(g,0,sizeof g);
    for(;m;m--){
        int a,b,c;
        cin>>a>>b>>c;
        g[a][b]=g[b][a]=(100.0-c)*1.0/100;
    }
    cin>>l>>r;
    printf("%.8lf\n",100.0/dijkstra());
    return 0;
}

对于dijkstra算法的核心,就是求最短路,但是dijkstra算法需要变通
这里有三种变通

1 超级源点

2 超级汇点

3 反向建边

4 多权边

我们先来一道关于反向求解问题
反向求解

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=60;
int g[N][N];
int d[N];
bool st[N];
int m;
int get(char a){
    if(a<='Z') return a-'A'+1;
    return a-'a'+27;
}
int dijkstra(){
    memset(d,0x3f,sizeof d);
    d[26]=0;
    for(int i=0;i<52;i++){
        int t=-1;
        for(int j=1;j<=52;j++)
            if(!st[j]&&(t==-1||d[t]>d[j]))
                t=j;
        st[t]=true;
        for(int j=1;j<=52;j++)
            d[j]=min(d[j],d[t]+g[t][j]);
    }
    int k=1;
    for(int i=2;i<26;i++)
        if(d[k]>d[i]) k=i;
    return k;
}
int main(){
    cin>>m;
    memset(g,0x3f,sizeof g);
    for(;m;m--){
        char a,b;
        int c;
        cin>>a>>b>>c;
        int x=get(a),y=get(b);
        g[x][y]=g[y][x]=min(g[x][y],c);
    }
    int k=dijkstra();
    cout<<char('A'+k-1)<<" "<<d[k];
    return 0;
}

来一道超级源点题

#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
typedef pair<int,int> PII;
const int N=1e6+10;
int n,m,k,q;
int h[N],e[N],ne[N],w[N],idx;
int d[N];
bool st[N];
void add(int a,int b,int c){
    e[idx]=b;w[idx]=c;ne[idx]=h[a];h[a]=idx++;
}
void dijkstra(){
    memset(d,0x3f,sizeof d);
    priority_queue<PII,vector<PII>,greater<PII> > heap;
    heap.push({0,0});
    d[0]=0;
    while(!heap.empty()){
        auto t=heap.top();
        heap.pop();
        int u=t.second,v=t.first;
        for(int i=h[u];i!=-1;i=ne[i]){
            int j=e[i];
            if(d[j]>d[u]+w[i]){
                d[j]=d[u]+w[i];
                heap.push({d[j],j});
            }
        }
    }
}
int main(){
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof h);
    while(m--){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
        add(b,a,c);
    }
    scanf("%d",&k);
    while(k--){
        int a;
        scanf("%d",&a);
        add(0,a,0);
    }
    dijkstra();
    scanf("%d",&q);
    while(q--){
        int a;
        scanf("%d",&a);
        printf("%d\n",d[a]);        
    }
    return 0;
}

这里再补充一道多权边的dijksstra算法

多权问题

因为是稀疏图,我们用邻接表

#include<iostream>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;
const int N=2010;
int h[N],e[N],ne[N],w[N],o[N],idx;
int dist[N],cost[N],p[N];
bool st[N];
int n,m,s,d;
void add(int a,int b,int c,int d){
    e[idx]=b;ne[idx]=h[a];w[idx]=c;o[idx]=d;h[a]=idx++;
}
void dijkstra(){
    memset(dist,0x3f,sizeof dist);
    memset(cost,0x3f,sizeof cost);
    dist[s]=0,cost[s]=0;
    for(int i=0;i<n;i++){
        int t=-1;
        for(int j=0;j<n;j++)
         if(!st[j]&&(t==-1||dist[t]>dist[j]))
            t=j;
        st[t]=true;
        for(int j=h[t];j!=-1;j=ne[j]){
            int k=e[j];
            if(dist[k]>dist[t]+w[j]){
                dist[k]=dist[t]+w[j];
                cost[k]=cost[t]+o[j];
                p[k]=t;
            }
            else if(dist[k]==dist[t]+w[j]){
                if(cost[k]>cost[t]+o[j]){
                    cost[k]=cost[t]+o[j];
                    p[k]=t;
                }
            }
        }
    }
}
int main(){
    cin>>n>>m>>s>>d;
    memset(h,-1,sizeof h);
    for(;m;m--){
        int a,b,c,d;
        cin>>a>>b>>c>>d;
        add(a,b,c,d);
        add(b,a,c,d);
    }
    dijkstra();
    stack<int> l;
    for(int i=d;i!=s;i=p[i]) l.push(i);
    l.push(s);
    while(l.size()) cout<<l.top()<<" ",l.pop();
    cout<<dist[d]<<" "<<cost[d];
    return 0;
}

我做的不是很多,也找不到很多简单的题,我很无奈,做不来。伤心太平洋


本人不怎么喜欢用邻接矩阵,主要是邻接矩阵太占空间(当然邻接矩阵方便且快)

这里我给你们看一下我的思路来源于这篇博客
-> 反向建边,超级源点,超级汇点
就是这篇博客让我发现,原来dijkstra算法的另一种开启方式,哈哈
感谢此博主,感谢感谢

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值