dijkstra(迪杰斯特拉)(朴素版)算法超详解

本文解析了如何使用Dijkstra算法解决求有向图中1号点到n号点的最短路径问题,涉及邻接矩阵存储和优先队列的使用,展示了算法的具体步骤和实现细节。
摘要由CSDN通过智能技术生成

⭐⭐⭐适用范围:求有权图的最短路径
(边权要是正数).

来个例题:

给定一个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

先看下纯享代码

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std
const int N = 510;

int n ,m;
int q[N][N];
int dist[N];
bool st[N];

 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;
          
        st[t] = true;
        
        for(int j = 1;j <= n;j ++)
            dist[j]= min(dist[j],dist[t] + q[t][j]);
    }
    if(dist[n] == 0x3f3f3f3f) return -1;
  
    return dist[n]; 
}
int main()
{
    cin >> n >> m;
    
    memset (q,0x3f,sizeof q);
    
    for(int i = 0;i < m;i ++)
    {
        int a,b,c;
        cin >> a >> b >>c;
        q[a][b] = min(q[a][b],c);
    }
    
    int t = dijkstra();
    
    cout << t << endl;
    
    return 0;
}

ok接下来解释下以及说一下自己的见解 ୧⍢⃝୨

主体思路:
①:是用邻接矩阵去存一下每个边权

就比如
1 2 2
表示 1到2的边权是2.

for(int i = 0;i < m;i ++)
    {        int a,b,c;  
          cin >> a >> b >>c;      
            q[a][b] = min(q[a][b],c);   
     }
     

在这段代码里就用 q[1][2] = 2 来储存
(题目里说会有重边,就是一个边会有多个边 权我们取最小的,而自环我们这个可以直接规避就是说不用管了。
对于重边,就比如它还有 1 2 5 什么之类的这时候那个min函数(因为只求最短路径所以是min函数)的作用就体现出来了

如果没有那么那个距离就是无穷(用0x3f3f3f3f来指带的)上面是用

memset 来直接初始化各个权边的

②:在dijkstra函数里面实现距离的累积

用dist数组来存起点到每个点的最小距离,走到最后一个点时也就是来存我们的答案,是靠一步一步叠加的.

int dijkstra()
   {
     memset(dist,0x3f,sizeof dist);
     dist[1] = 0;

先把距离给初始化,先是设置为无穷(和上面一样套路)但是要把起点给设置出来
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[t] > dist[j]))
         t = j;
       
     st[t] = true;
     for(int j = 1;j <= n;j ++)
         dist[j]= min(dist[j],dist[t] + q[t][j]);
 }
 if(dist[n] == 0x3f3f3f3f) return -1;

 return dist[n]; 
}

可以看到有三个for循环
首先第一个for循环仅仅代表

对于第二个for循环,是确定下一个最近的那个点也就是q[a][b]的a的值
if(!st[j]&&(t == -1||dist[t] > dist[j]))
看到此处的条件首先
(条件0&&(条件1||条件2))
只要条件0和后面无论是条件1还是2满足还是同时满足就执行if语句
也就是
0+1
0+2
0+1+2
形式都行

然后其中
用bool类型的st来表示有没有遍历过因为初始都是false嘛所以用 !st[j] .

对于(t == -1)表示是第一个进来的距离

对于(dist[t] > dist[j])
来说就是看那个最小那个就是那个下一个距离点

最后一个for是确定那个距离的
其实这三个是紧密联系的要一起看
举个例子

在这里插入图片描述

第一次i = 0
然后进入里面的第一个for循环
然后第一次进入时,就相当于是从一个虚无走向1这个点,后面的那个(dist[t] > dist[j])条件第一次是用不到的毕竟初始值都是无穷嘛。
也就是那个q[a][b]里面的a是t

再把它变为true表示已经确定过的点

然后在进行下一步的for循环,确定从t这个点出发到所有点的距离


 for(int j = 1;j <= n;j ++)
            dist[j]= min(dist[j],dist[t] + q[t][j]);

可以看到就是遍历从开头到每一个点的距离最小值
在这里插入图片描述
箭头上的数字表示距离
就是说
dist[1] = 0
dist[2] = 2
dist[3] = 无穷
dist[4] = 2
dist[5] = 无穷
可以看到当走不到的时候我们是用无穷来表示
毕竟初始值我们是设置无穷嘛。

然后进入下一个大循环 i = 1
在进入里面的第一个for循环

int t = -1;
for(int j = 1;j<= n;j ++)      
if(!st[j]&&(t == -1||dist[t] > dist[j]))       
      t = j;          

以下为对此代码的实例分析:

j = 1时 st[1] = true已经确定了所以不满足if的条件,j = 2
对照上面的dist[2] = 2
然后t就暂时 t = j = 2
因为后面还有没有遍历完的 3-4-5
所以会继续遍历, j=3时因为dist[3] = 无穷
在和dist[t] = 2比较显然不满足dist[t] > dist[j]
也不满足 t = -1,然后进入下一个 j=4
可以看到dist[4] = 1
满足dist[t] > dist[j]就会改变t = 4也就是把
q[a][b]里面的a = 4也就是第二次是从4这个
点来出发.
当然j=5显然也是不满足的我们直接跳过
进入下一个环节 先把它标记为st[4]=true

进入里面的第二个for循环

 for(int j = 1;j <= n;j ++)           
  dist[j]= min(dist[j],dist[t] + q[t][j]);

和上面一样
在这里插入图片描述

因为它下一个啥都没有所以都是无穷
(深入思考:这里自己也是无穷是因为看q[a][b]的实际q[4][4]就是无穷,而一开始的是我们自己设置的dist[1]=0 dist[j]=min(dist[j],dist[t] + q[t][j]);
可以看到虽然q[1][1]是无穷但是我们有min函数所以它会让它等于0)

然后这里有个min函数就是说
要和上一个
dist[1] = 0
dist[2] = 2
dist[3] = 无穷
dist[4] = 1
dist[5] = 无穷
与累加后的值进行比较
q[4][1]+dist[1] = 无穷
q[4][2]+dist[1] = 无穷
q[4][3]+dist[1] = 无穷
q[4][4]+dist[1] = 无穷
q[4][5]+dist[1] = 无穷
然后在取最小的那个更新
更新为:
dist[1] = 0
dist[2] = 2
dist[3] = 无穷
dist[4] = 1
dist[5] = 无穷

然后进入下一个大循环i = 2
和上面分析的一样
j =1和4已经被标记了所以就不会在进去
其实到这里我们就可以看出来
这第一个for循环就是来确定下一个被上一个点指向点的先后顺序(有点点绕,在这个例子里就是说1指向2和4然后判断是先从4开始还是从2开始我们这是小一点的先开始)

然后按照上面逻辑t的下一个值就是t=2
先标记st[2] = true
进入里面第二个for循环
在这里插入图片描述
同上面的逻辑
更新每个dist(这里直接给更新后的数据)
dist[1] = 0
dist[2] = 2
dist[3] = 3
dist[4] = 1
dist[5] = 无穷

(到这里我们可以恍然大悟了原来第二个for循环就是来确定每一次从1到这个点的最短距离)

然后进入i = 3循环
到这里应该比较清晰了
第一次的for循环t = j = 3嘛在标记下
第二次的for
我们直接更新数据
dist[1] = 0
dist[2] = 2
dist[3] = 3
dist[4] = 1
dist[5] = 4

此时i=3嘛这里就可以知道为什么是i<n-1
解释:
(我们这个例子n是5,就是说 不满足i<4就会退出
循环,然后我们的i下一步进入for循环刚好是i = 4就是说,会退出,
我们五点,用了四次大的for循环就可以出答案,其实这里我们也可以是i = 1;i<n
因为第一个点我们是直接设置了就为0所以是可以少一次)

出了循环体
然后最后一步判断

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

就是说如果还是无穷(用0x3f3f3f3f来代替的)那么说明不能走到,不然就是走到了
这里用不用else都行不用是为了少打几个字符

然后为什么是朴素版嘞,(你可以看到这里的里面的第二个for循环每次都是要遍历n次但是实际又不需要n次会浪费许多不必要的时间)

ok看到这里我们就把这个算法学完啦!!!
ε٩(๑> ₃ <)۶з

制作不易,给个免费的赞呗꒰ↀωↀ꒱❤❤

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值