【算法】求最短路径算法

news2024/11/13 23:25:22

文章目录

  • 一、迪杰斯特拉算法
    • 1.1 算法介绍
    • 1.2 算法步骤
    • 1.3 应用场景
  • 二、弗洛伊德算法
    • 2.1 算法介绍
    • 2.2 算法步骤
    • 2.3 应用场景

一、迪杰斯特拉算法

1.1 算法介绍

从某顶点出发,沿图的边到达另一顶点所经过的路径中,各边上权值之和最小的一条路径叫做最短路径。 解决最短路径的问题有以下算法:Dijkstra
算法,Bellman-Ford 算法,Floyd 算法和 SPFA 算法等。

  • 迪杰斯特拉算法(Dijkstra 算法)是典型最短路径算法,用于计算一个节点到其它节点的最短路径,它的主要特点是以起始点为中心向外层层扩展(广度优先搜索思想),直到扩展到终点为止。

  • 该算法的时间复杂度为 O(ElogV),其中 E 为边数,V 为节点数。

1.2 算法步骤

  1. 设置起始点,并有记录各顶点的数组A、记录各顶点到起始点距离的数组B(起始点到自身的距离记作0);
  2. 从距离数组中选取最小的值(即当前为最短路径),选好以后,将对应的顶点从数组A中移除,对应记录的距离从数组B中移除;
  3. 比较起始点与各顶点的距离值,更新前驱节点,并保留值较小的一个距离值,从而完成对各顶点到起始点距离的数组B的更新;
  4. 重复执行步骤2、步骤3,直到最短路径顶点为目标顶点时,算法结束。

1.3 应用场景

假设有 7 个村庄(A,B,C,D,E,F,G),各个村庄的距离用边线表示(权) ,比如 A<–>B 距离 5 公里。问:如何计算出 G
村庄到其它各个村庄的最短距离?如果从其它点出发到各个点的最短距离又是多少?(通过迪杰斯特拉算法求最短路径)

示意图:
在这里插入图片描述

代码示例:

public class DijkstraAlgorithmDemo {

    public static void main(String[] args) {
        // 顶点集合。
        char[] vertexes = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};

        // 邻接矩阵,`Integer.MAX_VALUE` 表示不联通。
        final int x = Integer.MAX_VALUE;
        int[][] matrix = {
                {x, 5, 7, x, x, x, 2},
                {5, x, x, 9, x, x, 3},
                {7, x, x, x, 8, x, x},
                {x, 9, x, x, x, 4, x},
                {x, x, 8, x, x, 5, 4},
                {x, x, x, 4, 5, x, 6},
                {2, 3, x, x, 4, 6, x}
        };

        // 创建图。
        Graph graph = new Graph(vertexes, matrix);
        // 查看矩阵创建。
        graph.showGraph();
        // [x, 5, 7, x, x, x, 2]
        // [5, x, x, 9, x, x, 3]
        // [7, x, x, x, 8, x, x]
        // [x, 9, x, x, x, 4, x]
        // [x, x, 8, x, x, 5, 4]
        // [x, x, x, 4, 5, x, 6]
        // [2, 3, x, x, 4, 6, x]

        // 测试迪杰斯特拉算法
        // [6]:"G"([下标]:对应顶点)。
        int vertexIndex = 6;
        graph.dijkstra(vertexIndex);
        graph.dijkstraResult(vertexes, vertexIndex);
        // 各顶点访问状态 (0:表示已访问 1:表示未访问): 1 1 1 1 1 1 1
        // 前一顶点: 6 6 0 5 6 6 0
        // 各顶点到起始顶点的距离: 2 3 9 10 4 6 0
        // 具体详情如下:<G-A>:(2) <G-B>:(3) <G-C>:(9) <G-D>:(10) <G-E>:(4) <G-F>:(6) <G-G>:(0)
    }
}


/**
 * 图
 */
class Graph {

    /**
     * 顶点数组。
     */
    private final char[] vertexes;

    /**
     * 邻接矩阵。
     */
    private final int[][] matrix;

    /**
     * 已经访问的顶点的集合。
     */
    private VisitedVertex visitedVertex;

    /**
     * 初始化构造。
     *
     * @param vertexes 顶点
     * @param matrix   矩阵
     */
    public Graph(char[] vertexes, int[][] matrix) {
        this.vertexes = vertexes;
        this.matrix = matrix;
    }

    /**
     * 展示dijkstra算法结果。
     *
     * @param vertexes    顶点数组
     * @param vertexIndex 顶点下标
     */
    public void dijkstraResult(char[] vertexes, int vertexIndex) {
        visitedVertex.show(vertexes, vertexIndex);
    }

    /**
     * 展示图。
     */
    public void showGraph() {
        for (int[] link : matrix) {
            // 将表示不联通的值替换输出(便于查看)。
            System.out.println(Arrays.toString(link)
                    .replaceAll(Integer.MAX_VALUE + "", "x")
            );
        }
    }

    /**
     * 迪杰斯特拉算法。
     * 目的:根据起始顶点下标找到最短路径。
     *
     * @param index 顶点对应的下标(比如:0对应顶点A,1对应顶点B...)
     */
    public void dijkstra(int index) {
        int vertexNum = this.vertexes.length;
        this.visitedVertex = new VisitedVertex(vertexNum, index);
        // 更新当前顶点到其它周围顶点的距离和前驱顶点。
        update(index);
        for (int i = 1; i < vertexNum; i++) {
            // 选择并返回新的访问顶点。
            index = this.visitedVertex.updateArray();
            // 更新当前顶点到其它顶点的距离和前驱顶点。
            update(index);
        }
    }

    /**
     * 更新下标顶点到周围顶点的距离及前驱顶点。
     *
     * @param index 对应下标
     */
    private void update(int index) {
        // 遍历邻接矩阵。
        int len = this.matrix[index].length;
        long distance = 0;
        for (int j = 0; j < len; j++) {
            // `distance`表示:当前顶点到起始顶点的距离 + 从当前顶点到j顶点距离的和。
            distance = this.visitedVertex.getDistance(index) + this.matrix[index][j];
            // 如果j顶点没有被访问过,且求得的距离小于起始顶点到j顶点的距离,就需要进行更新。
            if (!visitedVertex.in(j) && distance < this.visitedVertex.getDistance(j)) {
                // 更新j顶点前驱顶点。
                this.visitedVertex.updatePre(j, index);
                // 更加起始顶点到j顶点的距离。
                this.visitedVertex.updateDistance(j, distance);
            }
        }
    }
}


/**
 * 已访问的顶点集。
 */
class VisitedVertex {

    /**
     * 记录顶点是否被访问过。
     * 0表示未访问,1表示访问过。
     */
    private final int[] isVisited;

    /**
     * 记录前驱顶点。
     */
    private final int[] preVisited;

    /**
     * 记录到起始顶点距离。
     */
    private final long[] distances;

    /**
     * 使用 `Integer.MAX_VALUE` 表示无穷大,即不可联通。
     */
    private static final int DISCONNECTED = Integer.MAX_VALUE;

    /**
     * 初始化构造。
     *
     * @param vertexNum 顶点个数
     * @param index     顶点对应的下标(比如:0对应顶点A,1对应顶点B...)
     */
    public VisitedVertex(int vertexNum, int index) {
        this.isVisited = new int[vertexNum];
        this.preVisited = new int[vertexNum];
        this.distances = new long[vertexNum];
        // 初始化距离数组数据。
        Arrays.fill(distances, DISCONNECTED);
        // 将起始顶点标记为已访问,并将访问距离设置为0。
        this.isVisited[index] = 1;
        this.distances[index] = 0;
    }

    /**
     * 顶点是否被访问过。
     *
     * @param index 顶点下标
     * @return boolean
     */
    public boolean in(int index) {
        return 1 == this.isVisited[index];
    }

    /**
     * 更新距离。
     *
     * @param index    顶点下标
     * @param distance 距离
     */
    public void updateDistance(int index, long distance) {
        this.distances[index] = distance;
    }

    /**
     * 更新前驱顶点
     *
     * @param pre   前驱顶点下标
     * @param index 当前顶点下标
     */
    public void updatePre(int pre, int index) {
        preVisited[pre] = index;
    }

    /**
     * 得到到起始顶点距离。
     *
     * @param index 顶点下标
     * @return long
     */
    public long getDistance(int index) {
        return this.distances[index];
    }

    /**
     * 继续选择并返回新的访问顶点,比如这里的G 完后,就是 A点作为新的访问顶点(注意不是起始顶点)。
     *
     * @return int
     */
    public int updateArray() {
        long minDis = DISCONNECTED;
        int index = 0;
        for (int i = 0; i < isVisited.length; i++) {
            // 若没有被访问过且有权值,则进行更新。
            if (0 == isVisited[i] && minDis > distances[i]) {
                minDis = distances[i];
                index = i;
            }
        }

        // 设置已访问。
        isVisited[index] = 1;
        return index;
    }

    /**
     * 将各数组数据情况进行输出并显示最终结果。
     *
     * @param vertexes    顶点数组
     * @param vertexIndex 顶点下标
     */
    public void show(char[] vertexes, int vertexIndex) {

        System.out.println("==========================");

        System.out.print("各顶点访问状态 (0:表示已访问 1:表示未访问): ");
        for (int i : isVisited) {
            System.out.print(i + " ");
        }
        System.out.println();

        System.out.print("前一顶点: ");
        for (int i : preVisited) {
            System.out.print(i + " ");
        }
        System.out.println();

        System.out.print("各顶点到起始顶点的距离: ");
        for (long i : distances) {
            System.out.print(i + " ");
        }
        System.out.println();

        System.out.print("具体详情如下:");
        int counter = 0;
        for (long i : distances) {
            if (DISCONNECTED != i) {
                System.out.print("<" + vertexes[vertexIndex] + "-" + vertexes[counter] + ">:(" + i + ") ");
            } else {
                System.out.println("N ");
            }
            counter++;
        }
        System.out.println();
    }
}

二、弗洛伊德算法

2.1 算法介绍

弗洛伊德算法(Floyd 算法)又称为插点法,是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法,与 Dijkstra
算法类似。

  • 与迪杰斯特拉算法对比:

    • 迪杰斯特拉算法通过选定的被访问顶点,求出从起始访问顶点到其它顶点的最短路径;
    • 弗洛伊德算法中每一个顶点都是起始访问点,所以需要将每一个顶点看做被访问顶点,求出从每一个顶点到其它顶点的最短路径。
  • 该算法的时间复杂度为 O(n³),其中 n 为节点数。

  • 该算法可以处理有向图和无向图,但不能处理负权环,如果存在负权环,则最短路径不存在。

2.2 算法步骤

  1. 初始化距离矩阵:初始化一个 n*n 的矩阵D,其中 D[ i ][ j ] 表示从节点 i 到节点 j 的最短路径长度。如果 i 和 j 之间没有边相连,则 D[ i ][ j ] 为无穷大。
  2. 通过中间点更新距离矩阵:对于每个节点 k,依次更新矩阵D。具体地,对于每对节点 i 和 j,如果从 i 到 j 的路径经过节点 k 可以使得路径长度更短,则更新 D[ i ][ j ] 为 D[ i ][ k ]+D[ k ][ j ] 。
  3. 最终得到的矩阵D即为所有节点对之间的最短路径长度。

2.3 应用场景

假设有 7 个村庄(A,B,C,D,E,F,G),各个村庄的距离用边线表示(权),比如 A<–>B 距离 5
公里。问:如何计算出各村庄到其它各村庄的最短距离?(通过弗洛伊德算法求最短路径)

示意图:
在这里插入图片描述

代码示例:

public class FloydAlgorithmDemo {

    public static void main(String[] args) {
        // 顶点集合。
        char[] vertexes = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
        
        // 邻接矩阵,`Integer.MAX_VALUE` 表示不联通。
        final int x = Integer.MAX_VALUE;
        long[][] matrix = {
                {0, 5, 7, x, x, x, 2},
                {5, 0, x, 9, x, x, 3},
                {7, x, 0, x, 8, x, x},
                {x, 9, x, 0, x, 4, x},
                {x, x, 8, x, 0, 5, 4},
                {x, x, x, 4, 5, 0, 6},
                {2, 3, x, x, 4, 6, 0}
        };
        
        // 创建图。
        Graph graph = new Graph(vertexes.length, vertexes, matrix);
        // 测试弗洛伊德算法。
        graph.floyd();
        graph.show();
        // <A-A>的最短路径是0; <A-B>的最短路径是5; <A-C>的最短路径是7; <A-D>的最短路径是12; <A-E>的最短路径是6; <A-F>的最短路径是8; <A-G>的最短路径是2; 
        // <B-A>的最短路径是5; <B-B>的最短路径是0; <B-C>的最短路径是12; <B-D>的最短路径是9; <B-E>的最短路径是7; <B-F>的最短路径是9; <B-G>的最短路径是3; 
        // <C-A>的最短路径是7; <C-B>的最短路径是12; <C-C>的最短路径是0; <C-D>的最短路径是17; <C-E>的最短路径是8; <C-F>的最短路径是13; <C-G>的最短路径是9; 
        // <D-A>的最短路径是12; <D-B>的最短路径是9; <D-C>的最短路径是17; <D-D>的最短路径是0; <D-E>的最短路径是9; <D-F>的最短路径是4; <D-G>的最短路径是10; 
        // <E-A>的最短路径是6; <E-B>的最短路径是7; <E-C>的最短路径是8; <E-D>的最短路径是9; <E-E>的最短路径是0; <E-F>的最短路径是5; <E-G>的最短路径是4; 
        // <F-A>的最短路径是8; <F-B>的最短路径是9; <F-C>的最短路径是13; <F-D>的最短路径是4; <F-E>的最短路径是5; <F-F>的最短路径是0; <F-G>的最短路径是6; 
        // <G-A>的最短路径是2; <G-B>的最短路径是3; <G-C>的最短路径是9; <G-D>的最短路径是10; <G-E>的最短路径是4; <G-F>的最短路径是6; <G-G>的最短路径是0; 
    }
}

/**
 * 图。
 */
class Graph {

    /**
     * 顶点数组。
     */
    private final char[] vertexes;

    /**
     * 记录各个顶点到其它顶点的距离。
     */
    private final long[][] distances;

    /**
     * 到达目标顶点的前驱顶点。
     */
    private final int[][] preVisited;

    /**
     * 初始化构造。
     *
     * @param size      数组大小
     * @param vertexes  顶点集合
     * @param distances 距离
     */
    public Graph(int size, char[] vertexes, long[][] distances) {
        this.vertexes = vertexes;
        this.distances = distances;
        this.preVisited = new int[size][size];
        for (int i = 0; i < size; i++) {
            // 对前驱节点数组进行初始化(存放前驱顶点下标)。
            Arrays.fill(preVisited[i], i);
        }
    }

    /**
     * 展示前驱关系及距离信息。
     */
    public void show() {
        int length = this.distances.length;
        for (int k = 0; k < length; k++) {
            for (int i = 0; i < length; i++) {
                System.out.print("<" + this.vertexes[k] + "-" + this.vertexes[i] + ">的最短路径是" + distances[k][i] + "; ");
            }
            System.out.println();
        }
    }

    /**
     * 弗洛伊德算法。
     */
    public void floyd() {
        // 距离变量。
        long dist = 0;
        int dLen = this.distances.length;
        // 三层 `for`。
        // k 就是中间顶点的下标。
        for (int k = 0; k < dLen; k++) {
            // i 作为起始顶点。
            for (int i = 0; i < dLen; i++) {
                // j 作为目标顶点。
                for (int j = 0; j < dLen; j++) {
                    // 求出从 i 顶点出发,经过 k 中间顶点,到达 j 顶点距离。
                    dist = distances[i][k] + distances[k][j];
                    // 如果`算法得到的距离`小于`当前距离表中已有的距离`则进行更新。
                    if (dist < distances[i][j]) {
                        // 更新距离。
                        distances[i][j] = dist;
                        // 更新前驱顶点。
                        preVisited[i][j] = preVisited[k][j];
                    }
                }
            }
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/480171.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Hausdorff distance

Hausdorff距离量度度量空间中紧子集之间的距离 定义 设 X X X和 Y Y Y是度量空间 M M M的两个紧子集 d H ( X , Y ) max ⁡ { sup ⁡ x ∈ X inf ⁡ y ∈ Y d ( x , y ) , sup ⁡ y ∈ Y inf ⁡ x ∈ X d ( x , y ) } d_H\left(X, Y\right) \max \left\{\sup_{x\in X} \in…

Linux — 多线程的互斥与同步,信号量

1.线程互斥 进程线程间的互斥相关背景概念 临界资源&#xff1a;多线程执行流共享的资源就叫做临界资源。临界区&#xff1a;每个线程内部&#xff0c;访问临界资源的代码&#xff0c;就叫做临界区。互斥&#xff1a;任何时刻&#xff0c;互斥保证有且只有一个执行流进入临界区…

利用粒子群算法设计无线传感器网络中的最优安全路由模型(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 无线传感器网络&#xff08;WSN&#xff09;由数十个、数百个甚至数千个自主传感器组成。这些传感器以无线方式嵌入环境中&…

Day49 5.01 C++刷题

Go不是解释型语言&#xff0c;是编译型语言 Java是混合型语言

MOSFET正向导通,阻断,阈值电压研究

一 设计要求&#xff1a; N-为均匀掺杂、其他均为离子注入所形成的高斯掺杂&#xff1b;P的宽度为10、结深6um&#xff1b;氧化层oxide厚度为0.1um,宽度为10um&#xff1b;氧化层左侧空白需要定义为材料air&#xff1b;所有电极都定义为无厚度&#xff1b;所有的高斯掺杂峰值点…

( 数组和矩阵) 485. 最大连续 1 的个数 ——【Leetcode每日一题】

❓485. 最大连续 1 的个数 难度&#xff1a;简单 给定一个二进制数组 nums &#xff0c; 计算其中最大连续 1 的个数。 示例 1&#xff1a; 输入&#xff1a;nums [1,1,0,1,1,1] 输出&#xff1a;3 解释&#xff1a;开头的两位和最后的三位都是连续 1 &#xff0c;所以最大…

Vision Transformer架构Pytorch逐行实现

前言 代码来自哔哩哔哩博主deep_thoughts&#xff0c;视频地址&#xff0c;该博主对深度学习框架方面讲的非常详细&#xff0c;推荐大家也去看看原视频&#xff0c;不管是否已经非常熟练&#xff0c;我相信都能有很大收获。论文An Image is Worth 16x16 Words: Transformers f…

iOS审核这些坑,腾讯游戏也踩过

WeTest 导读 在App上架苹果应用商店的过程中&#xff0c;相信大多数iOS开发者往往都有过这样的经历&#xff1a;辛苦开发出来的产品&#xff0c;测试验收也通过了&#xff0c;满怀期待的提交App给苹果审核&#xff0c;结果经常被苹果各种理由拒之门外&#xff0c;苦不堪言。 …

Prometheus监控系统存储容量优化攻略,让你的数据安心保存!

云原生监控领域不可撼动&#xff0c;Prometheus 是不是就没缺点&#xff1f;显然不是。 一个软件如果什么问题都想解决&#xff0c;就会导致什么问题都解决不好。所以Prometheus 也存在不足&#xff0c;广受诟病的问题就是 单机存储不好扩展。 1 真的需要扩展容量吗&#xff…

0x80070570文件或目录损坏且无法读取解决方法

第一种解决方法&#xff1a;命令提示符修复。 1、首先按下“Win标R”键&#xff0c;打开运行。 2、然后如果要修复的文件在E盘&#xff0c;那就输入&#xff1a;chkdsk e: /f&#xff0c;h盘就是&#xff1a;chkdsk h: /f&#xff0c;反正是哪个盘就把中间的字幕改成那个盘的…

ecs思考

VPC网络诊断&#xff0c;从router看起&#xff0c;连接公有子网路有一个默认&#xff0c;再新增一条指向igw路由&#xff1b;连接私有子网路由有一个默认&#xff0c;再新增一条指向NAT网关的路由&#xff0c;其中NAT网关一定要在公有子网中&#xff0c;否则&#xff0c;私有子…

Android 10.0 设置默认浏览器后安装另外浏览器后默认浏览器功能修复

1.前言 在10.0的系统rom定制化开发中,当在系统中有多个浏览器的时候,会在用代码启用浏览器的时候,让用户选择进入哪个浏览器,这样显得特别的不方便 所以产品开发中,要求用RoleManager的相关api来设置默认浏览器,但是在设置完默认浏览器以后,在安装一款浏览器的时候,默认…

〔金融帝国实验室〕(Capitalism Lab)v9.0.00官方重大版本更新!

〖金融帝国实验室〗&#xff08;Capitalism Lab&#xff09;v9.0.00正式发布&#xff01; ◎制作发行&#xff1a;Enlight Software ◎发布时间&#xff1a;2023年04月28日 ————————————— ※v9.0.00更新说明&#xff1a; 1.实现6项数据信息双窗口并列显示&#…

兴寿镇“春踏青,兴寿行”特色旅游线路点靓辛庄

记者&#xff1a;云飞 踏着欢乐的节拍&#xff0c;伴着春日的暖阳&#xff0c;2023年4月29日&#xff0c;北京市昌平区兴寿镇&#xff0c;2023党建引领文旅农产业融合发展系列旅游季——“春踏青&#xff0c;兴寿行”特色旅游线路第二站&#xff0c;在兴寿镇辛庄村圆满举办。 此…

【搭建私有云盘】无公网IP,在外远程访问本地微力同步

文章目录 1.前言2. 微力同步网站搭建2.1 微力同步下载和安装2.2 微力同步网页测试2.3 cpolar的安装和注册 3.本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 1.前言 私有云盘作为云存储概念的延伸&#xff0c;虽然谈不上多么新颖&#xff0c;但是其…

《QDebug 2023年4月》

一、Qt Widgets 问题交流 二、Qt Quick 问题交流 1.对 qml 基本类型 list 的编辑 在 Qt5 中&#xff0c;QML 的 list 类型只提供了 push 添加数据&#xff0c;或者重新赋值&#xff0c;没法 pop。到了 Qt6&#xff0c;实测可以对 list 调用 pop/shift 等操作。 Qt5 中可以先…

【Liunx】进程的程序替换——自定义编写极简版shell

目录 进程程序替换[1~5]1.程序替换的接口&#xff08;加载器&#xff09;2.什么是程序替换&#xff1f;3.进程替换的原理4.引入多进程5.系列程序替换接口的详细解析&#xff08;重点&#xff01;&#xff09; 自定义编写一个极简版shell[6~8]6.完成命令行提示符7.获取输入的命令…

Docker 架构

Docker 架构 简介Docker daemon &#xff08;守护进程&#xff09;Docker client &#xff08;客户端&#xff09;Docker registries &#xff08;仓库&#xff09;Images &#xff08;镜像&#xff09;Containers &#xff08;容器&#xff09;The underlying technology &…

前缀和 技巧小记

前缀和 子数组的元素之和&#xff1a;一维前缀和子矩阵的元素之和&#xff1a;二维前缀和前缀和 哈希表&#xff1a;寻找和为 target 的子数组 子数组的元素之和&#xff1a;一维前缀和 前缀和适用于快速、频繁地计算一个索引区间内的元素之和。 int res 0; // 存储区间[…

链表:常见面试题-拷贝特殊链表

题目&#xff1a; 一种特殊的单链表节点类描述如下: class Node { int value; Node next; Node rand; Node(int val) {value val} } rand指针是单链表节点结构中新增的指针&#xff0c;rand可能指向链表中的任意一个节点&#xff08;包括自己&#xff09;&#xff0c;也可…