零基础学算法100天第1天——Dijkstra(图解最短路算法)(下)

简介: 零基础学算法100天第1天——Dijkstra(图解最短路算法)

🍅5.Dijkstra核心代码实现


       为了方便大家理解和记忆,我将代码按照上面的逻辑分成几个板块方便大家记忆。


1.初始化操作        

//所有距离初始化为正无穷
 Arrays.fill(dist,0x3f3f3f3f);
 //起点初始化为0,主要看起点的编号是几,这里默认为1
 dist[1]=0;


2.寻找找到距离起点最近且未标记过的点


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


       t表示距离起点最近且未标记过的点,刚开始默认为-1,表示没有,然后依次遍历所有的点,首先必须要保证该点未使用过,所以st[j]必须为flase。其次如果t=-1说明t还没选定,那么说明可以直接选择该点,然后如果当前dist[j]<dist[t]说明我们找到一个更小的点了,于是将t更新为这个更小的点。当然别忘记让st[t]为true。


3.利用步骤2找到的点去更新其他点的最短路径  


for(int j=1;j<=n;++j){
      dist[j]=Math.min(dist[j],dist[t]+g[t][j]);
 }


    判断的方法和我们前面的样例说的是一模一样的,这样更新下每个点就好。


   完整函数核心代码:


static int dijkstra(){
        //填充函数
        Arrays.fill(dist,0x3f3f3f3f);
        dist[1]=0;
        //因为总共有n个点,所以我们要迭代n次
        for(int i=0;i<n;++i){
            int t=-1;
            for(int j=1;j<=n;++j){
                if(!st[j]&&(t==-1||dist[t]>dist[j])) t=j;
            }
            if(t==n) break;
            st[t]=true;
             for(int j=1;j<=n;++j){
               dist[j]=Math.min(dist[j],dist[t]+g[t][j]);
           }
        }
        //当遍历完毕以后如果到点n的距离仍然是无穷大
        //说明无法从起点到达该点,我们返回-1,反正dist[n]就是到n的最短距离
        if(dist[n]==0x3f3f3f3f) return -1;
        else return dist[n];
    }


🍆6.最短路模板题练手


🍦1.网络延迟时间


image.png    


题目链接:网络延迟时间https://leetcode-cn.com/problems/network-delay-time/


      为了方便大家练习,我就从力扣上找了模板题给大家练习。


      首先大家遇到最短路径问题,一定要先分析数据规模,本题的n最大为100,所以我们是可以使用朴素的Dijkstra。我们直接套用上面的模板即可。


      但是这题有几个需要注意的地方:


1.什么时候返回-1?      


       因为要达到所有的点,所以当我们判断到某个点的最短路径是正无穷时,说明无法到达,返回-1


2.最大时间是多少?


      因为要保证所有节点都收到信息,所以所有结点收到信息的时间也就是最后一个结点收到的时间,我们在到达所有节点的世界里取一个max即可。


       代码转换:


class Solution {
    int N=110;
    int[][] g=new int[N][N];
    int[] dist=new int[N];
    boolean[] st=new boolean[N];
    public int networkDelayTime(int[][] times, int n, int k) {
        for(int i=0;i<=n;++i){
            Arrays.fill(g[i],0x3f3f3f3f);
        }
            for(int[] nums:times){
                int a=nums[0];
                int b=nums[1];
                int c=nums[2];
                g[a][b]=c;
            }
        return dijkstra(n,k);
    }
    int dijkstra(int n,int k){
        Arrays.fill(dist,0x3f3f3f3f);
        dist[k]=0;
        for(int i=0;i<n;++i){
            int t=-1;
            for(int j=1;j<=n;++j){
                if(!st[j]&&(t==-1||dist[j]<dist[t])) t=j;
            }
            st[t]=true;
            for(int j=1;j<=n;++j){
                dist[j]=Math.min(dist[j],dist[t]+g[t][j]);
            }
        }
        int res=0;
        for(int j=1;j<=n;++j){
            if(dist[j]==0x3f3f3f3f) return -1;
            res=Math.max(res,dist[j]);
        }
        return res;
    }
}


🍸2.到达目的的总方案数


你在一个城市里,城市由 n 个路口组成,路口编号为 0 到 n - 1 ,某些路口之间有 双向 道路。输入保证你可以从任意路口出发到达其他任意路口,且任意两个路口之间最多有一条路。


给你一个整数 n 和二维整数数组 roads ,其中 roads[i] = [ui, vi, timei] 表示在路口 ui 和 vi 之间有一条需要花费 timei 时间才能通过的道路。你想知道花费 最少时间 从路口 0 出发到达路口 n - 1 的方案数。


请返回花费 最少时间 到达目的地的 路径数目 。由于答案可能很大,将结果对 10^9 + 7 取余 后返回。


题目链接:到达目的地的方案数https://leetcode-cn.com/problems/number-of-ways-to-arrive-at-destination/        


       这道题是力扣某一周的周赛题,同样数据范围比较小,所以我们可以使用朴素的Dijkstra,但要注意这道题值的范围很大,所以无穷大值我们不能使用0x3f3f3f3f了。因为是统计到达的方案数,所以我们还需要使用一个cnt数组来统计到达每个位置的方案数,相当于是有点最短路+动态规划的感觉了。


       代码转换:

class Solution {
    int N=210;
    long[][] g=new long[N][N];
    boolean[] st=new boolean[N];
    long[] dist=new long[N];
    long INF = (Long.MAX_VALUE >> 1) - (long)1e5;
    int MOD = (int)1e9 + 7;
    int max=1;
    int[] cnt=new int[N];
    public int countPaths(int n, int[][] roads) {
        for(int i=0;i<n;++i){
          for(int j=0;j<n;++j){
              g[i][j]=INF;
          }
        }
        for(int[] s:roads){
            int a=s[0];
            int b=s[1];
            int w=s[2];
            g[a][b]=g[b][a]=w;
        }
        return Dijkstra(n);
    }
    int Dijkstra(int n){
        Arrays.fill(dist,INF);
        dist[0]=0;
        cnt[0]=1;
        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=0;j<n;++j){
                    if(dist[t]+g[t][j]<dist[j]){
                        dist[j]=dist[t]+g[t][j];
                        cnt[j]=cnt[t];
                    }else if(dist[t]+g[t][j]==dist[j]){
                        cnt[j]=(cnt[j]+cnt[t])%MOD;
                    }
             }                           
        }
        return cnt[n-1];
    }
}


🍓7.堆优化版Dijkstra


        朴素版的Dijkstra的时间复杂度为O(n^2),所以在点的数量变多时非常容易TLE,因此我们想到对原有的Dijkstra进行优化。


        那我们应该对哪一部分进行优化呢?


       我们有一个每次查找取出距离起点最小值的操作,所以我们可以想到利用优先队列进行优化,每次可以在O(1)的时间复杂度内取出距离起始点最近的点。


       思路过程:


       前面的过程一样,首先我们需要将起点放入优先队列中,然后进行一个while(!queue.isEmpty)的操作,类似BFS算法。每次取出距离最小的点,当然如果该点已经被取出来过那我们就应该continue,原理同朴素Dijkstra一样。然后我们同样使用该点进行相同的操作,去更新其他点的最短距离,每次更新一个点后,我们应该同时将其放入队列,让它继续去更新其他的边,因此最终完成的效率是O(mlogn),m是边数。


      完整代码转换:

import java.util.*;
public class Main {
    static int N=100010;
    static int n,m;
    static int[] dist=new int[N];
    static boolean[] st=new boolean[N];
    //领接表
    static Map<Integer,List<Node>> adj=new HashMap<>();
    public static void main(String[] args) {
        Scanner sc=new Scanner(System.in);
        n=sc.nextInt();
        m=sc.nextInt();
        while (m-->0){
            int a=sc.nextInt();
            int b=sc.nextInt();
            int w=sc.nextInt();
            if (!adj.containsKey(a)) adj.put(a,new ArrayList<>());
            adj.get(a).add(new Node(b,w));
        }
        int t=dijkstra();
        System.out.println(t);
    }
    //核心代码
    static int dijkstra(){
        Arrays.fill(dist,0x3f3f3f3f);
        dist[1]=0;
        PriorityQueue<int[]> heap=new PriorityQueue<>((a,b)->a[1]-b[1]);//小顶堆
        heap.offer(new int[]{1,0});
        while (!heap.isEmpty()){
            //每次放出距离最小的点
            int[] t=heap.poll();
            int ver=t[0],distance=t[1];
            //已经遍历过该点,直接跳过
            if (st[ver]) continue;
            st[ver]=true;
            //更新和最小节点相连的点
            List<Node> list=adj.get(ver);
            if (list==null) continue;
            for (Node node:list){
                int idx=node.idx;
                if (dist[idx]>distance+node.d){
                    dist[idx]=distance+node.d;
                    heap.offer(new int[]{idx,dist[idx]});
                }
            }
        }
        return dist[n]==0x3f3f3f3f?-1:dist[n];
    }
}
class Node{
    int idx;
    int d;
    public Node(int idx, int d) {
        this.idx = idx;
        this.d = d;
    }
}

       优化版的Dijkstra习题就没准备了,前面的两题都可以改成堆优化版的Dijkstra,大家也可以自行找习题训练。


相关文章
|
2月前
|
人工智能 算法 数据可视化
路径规划最全综述+代码+可视化绘图(Dijkstra算法+A*算法+RRT算法等)-2
路径规划最全综述+代码+可视化绘图(Dijkstra算法+A*算法+RRT算法等)-2
207 0
|
2月前
|
算法 Java C语言
第十四届蓝桥杯集训——练习解题阶段(无序阶段)-ALGO-5 算法训练 最短路
第十四届蓝桥杯集训——练习解题阶段(无序阶段)-ALGO-5 算法训练 最短路
24 0
|
3月前
|
算法
最短路之Dijkstra算法
最短路之Dijkstra算法
24 0
|
2月前
|
存储 人工智能 算法
路径规划最全综述+代码+可视化绘图(Dijkstra算法+A*算法+RRT算法等)-1
路径规划最全综述+代码+可视化绘图(Dijkstra算法+A*算法+RRT算法等)-1
70 0
|
1月前
|
算法
关于Dijkstra算法
关于Dijkstra算法
|
3月前
|
算法
bellman_ford算法与dijkstra为什么dijkstra算法不能计算带有负权边图
bellman_ford算法与dijkstra为什么dijkstra算法不能计算带有负权边图
22 0
|
3月前
|
算法
leetcode-675:为高尔夫比赛砍树 (最短路径算法bfs,dijkstra,A*)
leetcode-675:为高尔夫比赛砍树 (最短路径算法bfs,dijkstra,A*)
31 0
|
3月前
|
存储 算法 定位技术
图论算法dijkstra dfs bfs以及动态规划
图论算法dijkstra dfs bfs以及动态规划
34 0
|
4月前
|
算法 定位技术 索引
class064 Dijkstra算法、分层图最短路【算法】
class064 Dijkstra算法、分层图最短路【算法】
31 0
|
4月前
|
算法
dijkstra算法与bellman_ford 为什么dijkstra算法不能计算带有负权边图
为什么dijkstra算法不能计算带有负权边图bellman_ford算法增加功能检测负权回路(不存在最短路径)
40 0