[算法、读书笔记、C++,搜索与图论]dijkstra算法

介绍

Dijkstra算法是由荷兰计算机科学家狄克斯特拉于1959年提出的,因此又叫狄克斯特拉算法,是从一个顶点到其余各顶点的最短路径算法。

应用: 解决有权图中最短路径问题。
思想: 从起始点开始,采用贪心算法的策略,每次遍历到始点距离最近且未访问过的顶点的邻接点,直到扩展到终点为止。

下面,我们演示一下具体的过程
有向图 如下面所示:
在这里插入图片描述
起点(源点)为v1 ,终点为v4
从起点v1出开始遍历,标记v1已经被遍历过
遍历v1的邻接点,找到距离最近的邻接点 :v2
因为 v1->v2距离为7,v1->v4距离为70
从v2开始遍历,标记v2已经被遍历过了
遍历v2的邻接点,找到距离最近的邻接点 : v3
遍历v3的邻接点,找到距离最近的邻接点 : v4
故找到了v1到v4的最短路径为v1->v2->v3->v4 ,长度为18

伪代码:

const int N //图中节点个数
int dist[N]; //起点到节点i的路径长度
bool s[N];//当前节点是否已经去顶从起点到该店的最短路径
int  g[N][N];//邻接矩阵,存储图
int dijkstra(int start,int end){
    dist中起点位置初始化为0,其他位置初始化为∞(无穷大的数字)
    
    //遍历1~N节点
    for(int i=1;i<=N;i++){
           //把没有访问过的且距离起点最近的点赋值给t
           int t=-1;
           for(int j=1;j<=n;j++){
              if(!s[i]&&(t==-1||dist[t]>dist[j])){
                   t=j;
              }
           }
           //使用t更新邻接点的最短距离
           for(int j=1;j<=n;j++){
            if(dist[j]>dist[t]+g[t][j]){
                dist[j]=dist[t]+g[t][j];
            }
          }
    }
   //返回起点到终点的最短距离
   return dist[end];
}

题目练习

  1. Dijkstra求最短路 I

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为正值。 请你求出 1 号点到 n 号点的最短距离,如果无法从 1
号点走到 n 号点,则输出 −1。
输入格式 第一行包含整数 n 和 m。 接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

输出格式 输出一个整数,表示 1 号点到 n 号点的最短距离。 如果路径不存在,则输出 −1。

数据范围 1≤n≤500, 1≤m≤105, 图中涉及边长均不超过10000。

输入样例: 3 3 1 2 2 2 3 1 1 3 4
输出样例: 3

思路:
建立图,使用dijkstra算法求取答案即可。
代码:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;


const int N=500+10;

//邻接矩阵
int g[N][N];
bool s[N];
int dist[N];

int dijkstra(const int n){
//初始化
    memset(dist,0x3f,sizeof dist);
    memset(s,0,sizeof s);
    dist[1]=0;
    for(int i=1;i<n;i++){
        //找不在s中的距离最近的点,赋值给t
        int t=-1;
        for(int j=1;j<=n;j++){
           if(!s[j]&&(t==-1||dist[j]<dist[t])){
             t=j;
           }
        }
        //将t加入s
        s[t]=true;
        //用t更新其他点的距离
        for(int j=1;j<=n;j++){
            if(dist[j]>dist[t]+g[t][j]){
                dist[j]=dist[t]+g[t][j];
            }
        }
    }
    //如果走不到终点,则终点距离并不会被初始化
    if(dist[n]==0x3f3f3f3f){
        return -1;
    }
    return dist[n];
}

int main(){
//初始化,没有边的两点距离为无穷大
    memset(g,0x3f,sizeof g);
    
    int n=0,m=0;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int a=0,b=0,c=0;
        scanf("%d%d%d",&a,&b,&c);
        //两点之间可能包含重边,我们只需要保存最小的那条边
        g[a][b]=min(g[a][b],c);
    }
    printf("%d\n",dijkstra(n));
    return 0;
}

堆优化版本

在上面的dijkstra算法当中,时间复杂度为O(n^2);
倘若边数远小于n^2,我们需要对它进行优化的话, 遍历1~N节点 和 使用t更新邻接点的最短距离 这两方面,无法在进行简化。而对于 找不在s中的距离最近的点,赋值给t 我们可以使用堆来进行优化,取出最短路径的复杂度降为O(1);每次调整的复杂度降为O(elogn);e为该点的边数,所以复杂度降为O((m+n)logn)

伪代码

const int N //图中节点个数
const int M=N*N //图中有向边的数量
int dist[N]; //起点到节点i的路径长度
bool s[N];//当前节点是否已经去顶从起点到该店的最短路径
//邻接矩阵,存储图
int head[N],e[M],ne[M],w[M],idx=0;
int dijkstra(int start,int end){
    dist中起点位置初始化为0,其他位置初始化为∞(无穷大的数字)
    heap //小根堆
    heap.push(start);//把起点放入堆中
    //遍历1~N节点
    while(heap.size()){
       //找到距离最近且没有被访问过的点
        int t=heap.top();
        if(s[t]){
             continue;
        }
        s[t]=true;
        //使用t更新邻接点的最短距离
        for(int i=head[t];i!=-1;i=ne[i]){
             int j=e[i];
             if(dist[j]>dist[t]+w[i]){
                dist[j]=dist[t]+w[i];
                heap.push(dist[j]);
             }
        }

   }
   //返回起点到终点的最短距离
   return dist[end];
}

题目练习

  1. Dijkstra求最短路 II

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为非负值。 请你求出 1 号点到 n 号点的最短距离,如果无法从 1号点走到 n 号点,则输出 −1。

输入格式 第一行包含整数 n 和 m。 接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

输出格式 输出一个整数,表示 1 号点到 n 号点的最短距离。 如果路径不存在,则输出 −1。

数据范围 1≤n,m≤1.5×105, 图中涉及边长均不小于 0,且不超过 10000。

输入样例: 3 3 1 2 2 2 3 1 1 3 4
输出样例: 3

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;


const int N=1.5*1e5+10;
typedef pair<int,int>  PII;
//邻接矩阵
int head[N],w[N],e[N],ne[N],idx=0;

bool s[N];
int dist[N];

void add(int a,int b,int c){
    e[idx]=b,w[idx]=c,ne[idx]=head[a],head[a]=idx++;
}

int dijkstra(const int n){
    memset(dist,0x3f,sizeof dist);
    memset(s,0,sizeof s);
    dist[1]=0;
    
    priority_queue<PII,vector<PII>,greater<PII>>  heap;
    //距离   点
    heap.push({0,1});
    while(heap.size()){
         //找不在s中的距离最近的点,赋值给t
        auto tmp=heap.top();
        heap.pop();
        int t=tmp.second,dis=tmp.first;
        if(s[t]){
            continue;
        }
        //将t加入s
        s[t]=true;
         //用t更新其他点的距离
        for(int i=head[t];i!=-1;i=ne[i]){
            int j=e[i];
            if(dist[j]>dist[t]+w[i]){
                dist[j]=dist[t]+w[i];
                heap.push({dist[j],j});
            }
        }
       
    }
    
    if(dist[n]==0x3f3f3f3f){
        return -1;
    }
    return dist[n];
}

int main(){
    memset(head,-1,sizeof head);
    int n=0,m=0;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int a=0,b=0,c=0;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    printf("%d\n",dijkstra(n));
    return 0;
}


总结

朴素dijkstra算法,复杂度为O(n^2) ,主要适用于稠密图,即边数m接近于n*n (n为点数) ,图采用邻接矩阵存储

堆优化dijkstra算法,复杂度为O((m+n)logn),主要使用于稀疏图,即边数m远小于n*n (n为点数) ,图采用邻接表存储

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值