[Java学习]最短路径计算——Dijkstra算法

文章目录

  • 前言
  • 一、朴素Dijstra算法
    • 1.算法介绍
    • 2.具体题目描述:
  • 二、堆优化的Dijstra算法
    • 1.算法描述
    • 2.具体题目描述:
  • 总结


前言

最近在看关于求最短路径的一些算法,因此专门整理一下,以方便后续的复习。
求最短路径的情形主要分为以下两种:(1)单源最短路径;(2)多元汇最短路径。而单源最短路经中包括所有边数的权都是正数的,和边数有负数的这两种。针对不同的问题,可以分别用不同的算法进行求解。
下图表示不同问题所应用的不同算法,以及算法的复杂度:
[Java学习]最短路径计算——Dijkstra算法_第1张图片
这篇文章主要介绍一些Dijstra算法。


一、朴素Dijstra算法

1.算法介绍

朴素Dijstra主要用于在一个稠密图中,它的边数远远大于它的点数,因此我们可以用邻接矩阵来表示这个图。他的具体算法的设计思想如下:

输入:赋权的有向图 G = ( V , E , W ) G=(V,E,W) G=(V,E,W) V = { v 1 , v 2 , . . v n } V=\left\{v_1,v_2,..v_n\right\} V={v1,v2,..vn} s : = v 1 s:=v_1 s:=v1
输出:从源点s到所有的 v i ∈ V \ { s } v_i\in V\backslash\left\{s\right\} viV\{s} 的最短路径。
1.初始 S = { v 1 } S=\left\{v_1\right\} S={v1}
2.对于 v i ∈ V − S v_i\in V-S viVS,计算 d i s t [ s , v i ] dist\left[s,v_i\right] dist[s,vi]
3.对于选择 m i n v j ∈ V d i s t [ s , v i ] \mathop{min}\limits_{v_j\in V}dist\left[s,v_i\right] vjVmindist[s,vi],并将这个 v j v_j vj 放进集合 S S S 中,更新 V − S V-S VS 中的顶点的 d i s t dist dist 值;
4.重复1,直到 S = V S=V S=V

我们举一个具体例子来解释一下这个算法:
[Java学习]最短路径计算——Dijkstra算法_第2张图片
首先利用一个邻接矩阵来存储这个有向图:
[Java学习]最短路径计算——Dijkstra算法_第3张图片
接下来利用一个一维数组存储1号顶点到其余各个顶点的距离:
在这里插入图片描述
既然是求 1 号顶点到其余各个顶点的最短路程,那就先找一个离 1 号顶点最近的顶点。其中顶点2离顶点1距离最小,因此顶点2被确定下来。
接下来以顶点2为确定点,选择顶点2的两条出边:2->3,2->4这两条边。先讨论通过 2->3 这条边能否让 1 号顶点到 3 号顶点的路程变短。也就是说现在来比较 dis[3]和 dis[2]+e[2][3]的大小。其中 dis[3]表示 1 号顶点到 3 号顶点的路程,dis[2]+e[2][3]中 dis[2]表示 1 号顶点到 2 号顶点的路程,e[2][3]表示 2->3 这条边。所以 dis[2]+e[2][3]就表示从 1 号顶点先到 2 号顶点,再通过 2->3 这条边,到达 3 号顶点的路程。
我们发现:dis[3]=12, dis[2]+e[2][3]=10,因此dis[3]要更新为10。
同理通过2->4,可以将dis[4]更新为4。dis[4]>dis[3],因此选定顶点4作为确定点继续进行更新。顶点4的三条出边:3,5,6。此时dis[3]最小,因此选择dis[3]为确定点,进行下一轮迭代,以此类推,直到dis数组中全部为确定值,最终得到的dis矩阵:
在这里插入图片描述
输出dis[6],即最短路径为17.

2.具体题目描述:

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为正值。

请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1。

数据范围
1 ≤ n ≤ 500 1\leq n \leq 500 1n500
1 ≤ m ≤ 1 0 5 1\leq m \leq 10^5 1m105,
图中涉及边长均不超过10000。

最终的实现代码如下:

import java.io.*;
import java.util.*;

class Main {
    private static int N = 510;
    private static int[][] g = new int[N][N];  //为稠密阵所以用邻接矩阵存储
    private static boolean[] st = new boolean[N];  用于记录该点的最短距离是否已经确定
    private static int[] dist = new int[N];  //用于记录每一个点距离第一个点的距离
    private static int n;
    private static int max = 5000000; 

    public static void main(String[] args) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        String[] str1 = bufferedReader.readLine().split(" ");
        n = Integer.parseInt(str1[0]);
        int m = Integer.parseInt(str1[1]);

        for (int i = 1; i <= n; i ++) {
            Arrays.fill(g[i], max); //初始化图 因为是求最短路径 所以每个点初始为无限大
        }
        for (int i = 0; i < m; i ++) {
            String[] str2 = bufferedReader.readLine().split(" ");
            int a = Integer.parseInt(str2[0]);
            int b = Integer.parseInt(str2[1]);
            int c = Integer.parseInt(str2[2]);
            g[a][b] = Math.min(g[a][b], c);  //如果发生重边的情况则保留最短的一条边
        }

        System.out.println(dijkstra());
        bufferedReader.close();
    }

    public static int dijkstra() {
        Arrays.fill(dist, max);  //初始化距离  0x3f代表无限大
        dist[1] = 0;  //第一个点到自身的距离为0

        for (int i = 0; i < n; i ++) {
            int t = -1; //t存储当前访问的点
            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] = Math.min(dist[j], dist[t] + g[t][j]); //依次更新每个点所到相邻的点路径值
            }
        }
        if (dist[n] == max) {
            return -1;
        } else {
            return dist[n];
        }
    }
}

二、堆优化的Dijstra算法

1.算法描述

堆优化的Dijstra算法相比于朴素Dijstra算法,复杂度降低了,它主要适用于稀疏图的情况,即有向图的顶点和边数数量相似。因此需要利用邻接表来实现,而不能用之前的邻接矩阵。
堆优化的Dijstra算法主要利用了优先队列的一些方法,算法流程与朴素Dijstra算法类似,下面对应一个具体问题来了解。

2.具体题目描述:

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为非负值。

请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1。

数据范围:
1 ≤ n , m ≤ 1.5 ∗ 1 0 5 1\leq n,m \leq 1.5 *10^5 1n,m1.5105,

代码如下:

import java.io.*;
import java.util.*;

class Main {
    private static int N = 150010;
    private static int[] h = new int[N], e = new int[N], ne = new int[N], w = new int[N]; // 创建邻接表
    private static int max = 50000000;
    private static boolean[] st = new boolean[N];
    private static int[] dist = new int[N]; // // 存储1号点到每个点的最短距离
    private static int idx = 0;
    private static int n;

    public static void main(String[] args) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        String[] str1 = bufferedReader.readLine().split(" ");
        n = Integer.parseInt(str1[0]);
        int m = Integer.parseInt(str1[1]);

        Arrays.fill(h, -1);

        for (int i = 0; i < m; i ++) {
            String[] str2 = bufferedReader.readLine().split(" ");
            int a = Integer.parseInt(str2[0]);
            int b = Integer.parseInt(str2[1]);
            int c = Integer.parseInt(str2[2]);
            add(a, b, c);
        }

        int t = dijkstra();
        System.out.println(t);
        bufferedReader.close();
    }

    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中标记过且离源点最近的点
        Arrays.fill(dist, max);
        Queue<int[]> queue = new PriorityQueue<>(n, new Comparator<int[]> () {
           public int compare(int[] a, int[] b) {
               return a[1] - b[1];
           } 
        });
        queue.offer(new int[] {1, 0});
        dist[1] = 0;
        while (!queue.isEmpty()) {
        //1、找到当前未在s中出现过且离源点最近的点
            int[] p = queue.poll();
            int ver = p[0];
            int distance = p[1];
            if (st[ver]) {
                continue;
            }
            /2、将该点进行标记
            st[ver] = true;
            //3、用t更新其他点的距离
            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];
                    queue.offer(new int[] {j, dist[j]});
                }
            }
        }
        if (dist[n] == max) {
            return -1;
        } else {
            return dist[n];
        }
    }
}


总结

Dijstra算法主要用于一个边数权值均为正值的有向图,对于稀疏图和稠密图也应该选择不同的算法进行使用。

你可能感兴趣的:(算法,java,图论)