Dijkstra求最短路(图解)

你好,我是Hasity。

今天分享的内容:Dijkstra求最短路这个题目

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。

示例:

Dijkstra求最短路(图解)_第1张图片

思路分析

迪杰斯特拉算法采用的是一种贪心的策略。求源点到其余各点的最短距离步骤如下:

  1. 用一个 dist 数组保存源点到其余各个节点的距离,dist[i] 表示源点到节点 i 的距离。初始时,dist 数组的各个元素为无穷大。
    用一个状态数组 state 记录是否找到了源点到该节点的最短距离,state[i] 如果为真,则表示找到了源点到节点 i 的最短距离,state[i] 如果为假,则表示源点到节点 i 的最短距离还没有找到。初始时,state 各个元素为假。

Dijkstra求最短路(图解)_第2张图片

  1. 源点到源点的距离为 0。即dist[1] = 0。Dijkstra求最短路(图解)_第3张图片

  2. 遍历 dist 数组,找到一个节点,这个节点是:没有确定最短路径的节点中距离源点最近的点。假设该节点编号为 i。此时就找到了源点到该节点的最短距离,state[i] 置为 1。Dijkstra求最短路(图解)_第4张图片

  3. 遍历 i 所有可以到达的节点 j,如果 dist[j] 大于 dist[i] 加上 i -> j 的距离,即 dist[j] > dist[i] + w[i][j](w[i][j] 为 i -> j 的距离) ,则更新 dist[j] = dist[i] + w[i][j]。

Dijkstra求最短路(图解)_第5张图片

  1. 重复 3 4 步骤,直到所有节点的状态都被置为 1。
  • 更新与序号1相连接的节点(2,3,4)的dist,离源点1距离最近的状态为0的节点是2,将节点2的state改为1,更新与序号2相连接的节点5的dist

  • 离源点1距离最近的状态为0的的节点是4,将节点4的state改为1,更新与序号4相连接的节点5的dist

  • 离源点1距离最近的状态为0的的节点是3,将节点3的state改为1,更新与序号3相连接的节点4的dist

  • 离源点1距离最近的状态为0的的节点是5,将节点5的state改为1,更新与序号5相连接的节点的的dist(没有也需要遍历一遍)

Dijkstra求最短路(图解)_第6张图片

  1. 此时 dist 数组中,就保存了源点到其余各个节点的最短距离。

Dijkstra求最短路(图解)_第7张图片

思考:我们用什么数据结构表示图的任意顶点之间的连接关系呢?

邻接表和邻接矩阵是图的两种常用存储表示方式,用于记录图中任意两个顶点之间的连通关系,包括权值。

对于无向图 graph和有向图digraph

Dijkstra求最短路(图解)_第8张图片

Dijkstra求最短路(图解)_第9张图片

  • 选择一:邻接表

无向图 graph 表示

Dijkstra求最短路(图解)_第10张图片

有向图 digraph 表示

Dijkstra求最短路(图解)_第11张图片

  • 选择二:邻接矩阵

无向图 graph 表示

Dijkstra求最短路(图解)_第12张图片

有向图 digraph 表示

Dijkstra求最短路(图解)_第13张图片

由题中的1≤n≤500的数据量较小,我们采用邻接矩阵的方法代码更容易实现

关于领接表的优缺点:大家可以看这一篇文章:

代码实现
import java.util.*;
public class Main{
    static int N = 510,n,m, max = 0x3f3f3f3f;
    static int[][] g = new int[N][N];//存每个点之间的距离
    static int[] dist = new int[N];//存每个点到起点之间的距离
    static boolean[] st = new boolean[N];//存已经确定了最短距离的点
    public static int dijkstra(){
        Arrays.fill(dist,max);//将dist数组一开始赋值成较大的数
        dist[1] = 0; //首先第一个点是零

        //从0开始,遍历n次,一次可以确定一个最小值
        for(int i = 0 ; i < n ; i ++ ){ 
            int t = -1; //t这个变量,准备来说就是转折用的
            for(int j = 1 ; j <= n ; j ++ ){
                /***
                 * 因为数字是大于1的,所以从1开始遍历寻找每个数
                 * 如果s集合中没有这个数
                 * 并且t == -1,表示刚开始 或者 后面的数比我心找的数距离起点的距离短
                 * 然后将j 的值赋值给 t
                 ***/
                if(!st[j] && (t == -1 || dist[j] < dist[t])){
                    t = j; 
                }
            }

            st[t] = true;//表示这个数是已经找到了确定了最短距离的点

            //用已经确认的最短距离的点来更新后面的点
            //就是用1到t的距离加上t到j的距离来更新从1到j的长度
            for(int j = 1 ; j <= n ; j ++ ){
                //
                dist[j] = Math.min(dist[j],dist[t] + g[t][j]);
            }
        }
        //如果最后n的长度没有改变,输出-1,没有找到;否则输出最短路n
        if(dist[n] == max) return -1;
        else return dist[n];

    }
    public static void main(String[] args){
        Scanner scan = new Scanner(System.in);
        n = scan.nextInt();
        m = scan.nextInt();
        //将他们每个点一开始赋值成一个较大的值
        for(int i = 1 ; i <= n ; i ++ ){
            Arrays.fill(g[i],max);
        }
        while(m -- > 0){
            int a = scan.nextInt();
            int b = scan.nextInt();
            int c = scan.nextInt();
            g[a][b] = Math.min(g[a][b],c);//这个因为可能存在重边,所以泽出最短的
        }
        int res = dijkstra();
        System.out.println(res);
    }
}
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。
数据保证:如果最短路存在,则最短路的长度不超过 109

示例:

Dijkstra求最短路(图解)_第14张图片

思路分析

这道题和上一道没有什么区别,差别只有数据范围的变化

第一题:

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

第二题:

1≤n,m≤1.5×105
图中涉及边长均不小于 0,且不超过 10000。
数据保证:如果最短路存在,则最短路的长度不超过 109

我们可以对比发现,节点n的取值变大了,那么按照之前的时间复杂度O(n2)是肯定会超时的,所以我们需要降低时间复杂度,使用优先队列(小根堆)解决,并且随着n的取值变大,我们使用领接表来替代邻接矩阵存储图之间的关系

第一题:找到未标记的离源点最近的点 O(n2)

   for(int i = 0 ; i < n ; i ++ ){ 
            int t = -1; //t这个变量,准备来说就是转折用的
            for(int j = 1 ; j <= n ; j ++ ){
                /***
                 * 因为数字是大于1的,所以从1开始遍历寻找每个数
                 * 如果s集合中没有这个数
                 * 并且t == -1,表示刚开始 或者 后面的数比我心找的数距离起点的距离短
                 * 然后将j 的值赋值给 t
                 ***/
                if(!st[j] && (t == -1 || dist[j] < dist[t])){
                    t = j; 
                }
            }
            

算法的主要耗时的步骤是从dist 数组中选出:没有确定最短路径的节点中距离源点最近的点 t。只是找个最小值而已,没有必要每次遍历一遍dist数组。在一组数中每次能很快的找到最小值,很容易想到使用优先队列(小根堆)

Dijkstra求最短路(图解)_第15张图片

Dijkstra求最短路(图解)_第16张图片

Dijkstra求最短路(图解)_第17张图片

代码实现
import java.util.*;
class PIIs implements Comparable{
    private int first;//距离值
    private int second;//点编号

    public int getFirst()
    {
        return this.first;
    }
    public int getSecond()
    {
        return this.second;
    }
    public PIIs(int first,int second)
    {
        this.first = first;
        this.second = second;
    }
    @Override
    public int compareTo(PIIs o) {
        // TODO 自动生成的方法存根
        return Integer.compare(first, o.first);
    }
}
public class Main{
    static int N = 150010,n,m,idx,max = 0x3f3f3f3f;
    static int [] h = new int[N],e = new int[N],ne = new int[N],w = new int[N];
    static int[] dist = new int[N];
    static boolean[] st = new boolean[N];
    public static void add(int a,int b,int c){
        e[idx] = b;
        w[idx] = c;
        ne[idx] = h[a];
        h[a] = idx++;
    }
    public static int dijkstra()
    {
        //优先队列,保证每次取出都是最小值
        //维护当前未在st中标记过且离源点最近的点   小跟堆
        PriorityQueue queue = new PriorityQueue();
        Arrays.fill(dist, max);
        dist[1] = 0;
        queue.add(new PIIs(0,1));
        while(!queue.isEmpty())
        {
            //1、找到当前未在s中出现过且离源点最近的点
            PIIs p = queue.poll();
            int distance = p.getFirst();
            int t = p.getSecond();
            if(st[t]) continue;
            //2、将该点进行标记
            st[t] = true;
            //3、用t更新其他点的距离
            for(int i = h[t];i != -1;i = ne[i])//不要被这个遍历误导,这只是一个遍历循环而已,i只是下一个点的下标
            {
                int j = e[i];// i只是个下标,e中在存的是i这个下标对应的点和值。
                if(dist[j] > distance + w[i])
                {
                    dist[j] = distance + w[i];
                    queue.add(new PIIs(dist[j],j));
                }
            }

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

    public static void main(String[] args){
        Scanner scan = new Scanner(System.in);
        n = scan.nextInt();
        m = scan.nextInt();
        Arrays.fill(h,-1);
        while(m -- > 0){
            int a = scan.nextInt();
            int b = scan.nextInt();
            int c = scan.nextInt();
            add(a,b,c);
        }
        int res = dijkstra();
        System.out.println(res);
    }
}

总结

迪杰斯特拉算法适用于求正权有向图中,源点到其余各个节点的最短路径。注意:图中可以有环,但不能有负权边。

例如:如下图就不能使用迪杰斯特拉算法求节点 1 到其余各个节点的最短距离。

Dijkstra求最短路(图解)_第18张图片

你可能感兴趣的:(算法基础,算法)