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


前言

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

我们举一个具体例子来解释一下这个算法:
在这里插入图片描述
首先利用一个邻接矩阵来存储这个有向图:
在这里插入图片描述
接下来利用一个一维数组存储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算法主要用于一个边数权值均为正值的有向图,对于稀疏图和稠密图也应该选择不同的算法进行使用。

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,以下是带权图的最短路径算法——Dijkstra算法Java实现: ```java import java.util.*; public class DijkstraAlgorithm { private static final int MAX = Integer.MAX_VALUE; public static void dijkstra(int[][] graph, int src) { int n = graph.length; int[] dist = new int[n]; boolean[] visited = new boolean[n]; // 初始化dist数组和visited数组 for (int i = 0; i < n; i++) { dist[i] = MAX; visited[i] = false; } dist[src] = 0; for (int i = 0; i < n - 1; i++) { // 找到dist值最小的点 int u = minDistance(dist, visited); visited[u] = true; // 更新u的邻接点的dist值 for (int v = 0; v < n; v++) { if (!visited[v] && graph[u][v] != 0 && dist[u] != MAX && dist[u] + graph[u][v] < dist[v]) { dist[v] = dist[u] + graph[u][v]; } } } // 输出最短路径 printSolution(dist); } private static int minDistance(int[] dist, boolean[] visited) { int min = MAX; int minIndex = -1; for (int v = 0; v < dist.length; v++) { if (!visited[v] && dist[v] <= min) { min = dist[v]; minIndex = v; } } return minIndex; } private static void printSolution(int[] dist) { System.out.println("Vertex \t Distance from Source"); for (int i = 0; i < dist.length; i++) { System.out.println(i + " \t\t " + dist[i]); } } public static void main(String[] args) { int[][] graph = {{0, 2, 0, 6, 0}, {2, 0, 3, 8, 5}, {0, 3, 0, 0, 7}, {6, 8, 0, 0, 9}, {0, 5, 7, 9, 0}}; dijkstra(graph, 0); } } ``` 以上代码中,我们首先定义了一个`MAX`常量表示整数的最大值。然后,我们通过`dijkstra()`方法实现Dijkstra算法。在该方法中,我们首先初始化一个`dist`数组和一个`visited`数组,分别用于存储各个节点的距离和是否已经访问过。然后,我们将起始节点的距离设为0,其余节点的距离设为`MAX`。 接下来,我们在循环中找到`dist`值最小的未访问过的节点,将其标记为已访问,并更新其邻接点的距离。在循环结束后,我们将输出最短路径,即输出各个节点距离起点的距离。 最后,我们通过`main()`方法测试了上面的代码,输出的结果为: ``` Vertex Distance from Source 0 0 1 2 2 3 3 6 4 8 ``` 这说明从节点0到各个节点的最短路径分别为0、2、3、6和8。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值