算法:通过迪杰斯特拉(Dijkstra)算法,求出图中任意顶点到其他顶点的最短路径

2023-10-30

请看如下的示例图,该图有 V1-V7 七个顶点,每个顶点之间的距离、路径走向如图所示:
在这里插入图片描述
假设这是一幅地图,我们很多时候都需要搜路径,比如从家到公司的路线图(也就是说,家是一个点,公司是另一个点)。上图的各个点可以想象成分岔路口。当然,如果你不在分岔路口,你在某条路上,也可以理解为你在一个有两条路的分岔路口上,也就是向前行或者向后退。由于有单行道的存在,所以有的路是只能单向通行的(本示例图全部为单行道)。现在问题来了,假设上面的示例图是一个地图,你的家在V1点,你的公司在V6点,你走什么样的路线,才能走最短的路径到公司呢?有没有一个算法,可以求出从你家V1为起点,到所有其他点的最短路径呢?

这就是图论里面的最短路径算法,由于每一条通路都是单向的,所以又叫单源最短路径问题。解决单源最短路径问题的一般方法叫做Dijkstra算法。这个有30年历史的解法是贪婪算法的最好例子。话不多说,精髓全在代码和其间的注释里,可复制到IDE中运行:

1、图的顶点类,包含一到多个边:

import java.util.HashSet;
import java.util.Set;

/**
 * 图的顶点类
 * @Author: LiYang
 * @Date: 2019/9/8 21:32
 */
public class Vertex {

    //顶点的数据
    public String value;

    //顶点的边的集合(其相邻的顶点和权重)
    public Set<Edge> neighbors = new HashSet<>();

    /**
     * 空构造方法
     */
    public Vertex(){

    }

    /**
     * 构造方法,输入顶点值
     * @param value 顶点值
     */
    public Vertex(String value) {
        this.value = value;
    }

    /**
     * 为该顶点加入边
     * @param neighbor 边的另一顶点
     * @param weight 边的权重
     */
    public void addEdge(Vertex neighbor, int weight){
        neighbors.add(new Edge(neighbor, weight));
    }

    /**
     * 重新toString()方法,打印顶点的值
     * @return
     */
    @Override
    public String toString() {
        return value;
    }

}

2、图的边类,包含目的顶点,以及权重(距离):

/**
 * 图的边类,带权,本图例是单向边
 * Edge是属于某个顶点的,所以出发点是该顶点
 * Edge属性只有目的点,以及权重
 * @Author: LiYang
 * @Date: 2019/9/8 21:32
 */
public class Edge {

    //边的终点(邻居)
    public Vertex neighbor;

    //权重(距离)
    public int weight;

    /**
     * 无参构造
     */
    public Edge(){

    }

    /**
     * 全参数构造
     * @param neighbor 邻接点
     * @param weight 权重
     */
    public Edge(Vertex neighbor, int weight) {
        this.neighbor = neighbor;
        this.weight = weight;
    }

}

3、迪杰斯特拉(Dijkstra)算法表类,用于动态统计路径总距离和顶点关系的表

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 迪杰斯特拉(Dijkstra)算法表
 * @Author: LiYang
 * @Date: 2019/9/11 21:26
 */
public class DijkstraTable {

    //顶点
    public Vertex vertex;

    //是否已访问
    public boolean known;

    //总距离
    public int distance;

    //上一顶点
    public Vertex previous;

    /**
     * 初始化迪杰斯特拉(Dijkstra)算法表
     * @param vList 顶点集合
     * @return 迪杰斯特拉(Dijkstra)算法表
     */
    public static Map<String, DijkstraTable> initialDijkstraTable(List<Vertex> vList){
        //最后返回的迪杰斯特拉(Dijkstra)算法表
        Map<String, DijkstraTable> dijkstraTableMap = new HashMap<>();

        //开始循环添加
        for (Vertex vertex : vList){
            //当前节点对应的迪杰斯特拉(Dijkstra)算法表的行
            DijkstraTable dijkstraTable = new DijkstraTable();

            //顶点
            dijkstraTable.vertex = vertex;

            //初始化为未访问
            dijkstraTable.known =false;

            //初始化距离为无穷大
            dijkstraTable.distance = Integer.MAX_VALUE;

            //上一顶点初始化置空
            dijkstraTable.previous = null;

            //以顶点名为键,放入Map中
            dijkstraTableMap.put(vertex.value, dijkstraTable);
        }

        //返回初始化好了的迪杰斯特拉(Dijkstra)算法表
        return dijkstraTableMap;
    }

    /**
     * 寻找迪杰斯特拉(Dijkstra)算法表中距离最小的value的key
     * @param dijkstraTableMap 待查找的迪杰斯特拉(Dijkstra)算法表
     * @return 迪杰斯特拉(Dijkstra)算法表最小距离的key
     */
    public static String getMinimumDijkstraTableKey(Map<String, DijkstraTable> dijkstraTableMap){
        //最小距离
        int minimunDistance = Integer.MAX_VALUE;

        //第一次遍历普利姆(Prim)算法表,找出最小值
        for (Map.Entry<String, DijkstraTable> item : dijkstraTableMap.entrySet()){
            //如果已经访问过,则跳过
            if (item.getValue().known){
                continue;
            }

            //得到当前的距离
            int currentDistance = item.getValue().distance;

            //更新最小值
            if (minimunDistance > currentDistance){
                minimunDistance = currentDistance;
            }
        }

        //根据找到的最小值,找到最小值的key
        for (Map.Entry<String, DijkstraTable> item : dijkstraTableMap.entrySet()){
            //如果已经访问过,则跳过
            if (item.getValue().known){
                continue;
            }

            //得到当前的距离
            int currentDistance = item.getValue().distance;

            //如果当前距离就是最小距离,则返回其key
            if (currentDistance == minimunDistance){
                return item.getKey();
            }
        }

        //如果找不到,则表示迪杰斯特拉(Dijkstra)算法终止
        //因为算法while循环中加入了判断,所以不会到这里
        return "DijkstraFinished";
    }

    /**
     * 重新toString()方法,方便调试查看数据
     * @return
     */
    @Override
    public String toString() {
        return "DijkstraTable{" +
                "vertex=" + vertex +
                ", known=" + known +
                ", distance=" + distance +
                ", previous=" + previous +
                '}';
    }

}

4、图类,里面包含创建上面示例图的方法,以及迪杰斯特拉(Dijkstra)算法求最短路径的方法,还有运行的main方法:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * 数据结构与算法之:图
 * @Author: LiYang
 * @Date: 2019/9/11 21:42
 */
public class Graph {

    /**
     * 构建迪杰斯特拉(Dijkstra)算法所需要的图(本文刚开始的示例图)
     * 注意,迪杰斯特拉(Dijkstra)算法需要连通图,本例是有向连通图
     * 如果无向图,就双方都addEdge,有向图,就双方或单方addEdge
     * @return 图的顶点集合
     */
    public static List<Vertex> buildDijkstraGraph(){
        //创建顶点
        Vertex vertex1 = new Vertex("V1");
        Vertex vertex2 = new Vertex("V2");
        Vertex vertex3 = new Vertex("V3");
        Vertex vertex4 = new Vertex("V4");
        Vertex vertex5 = new Vertex("V5");
        Vertex vertex6 = new Vertex("V6");
        Vertex vertex7 = new Vertex("V7");

        //为顶点创建边
        //注意,本算法用到的图,是有向图,
        //两个顶点间并不一定互通
        vertex1.addEdge(vertex2, 2);
        vertex1.addEdge(vertex4, 1);

        vertex2.addEdge(vertex5, 10);
        vertex2.addEdge(vertex4, 3);

        vertex3.addEdge(vertex1, 4);
        vertex3.addEdge(vertex6, 5);

        vertex4.addEdge(vertex3, 2);
        vertex4.addEdge(vertex5, 2);
        vertex4.addEdge(vertex6, 8);
        vertex4.addEdge(vertex7, 4);

        vertex5.addEdge(vertex7, 6);

        //因为图中V6顶点只有到达它的边,它没有出去的边
        //所以,这里vertex6就不addEdge了

        vertex7.addEdge(vertex6, 1);

        //所有顶点的集合
        List<Vertex> vList = new ArrayList<>();
        vList.add(vertex1);
        vList.add(vertex2);
        vList.add(vertex3);
        vList.add(vertex4);
        vList.add(vertex5);
        vList.add(vertex6);
        vList.add(vertex7);

        //返回所有顶点
        return vList;
    }

    /**
     * 迪杰斯特拉(Dijkstra)算法,求最短路径
     * @param vList 图的顶点的集合
     */
    public static void dijkstraAlgorithm(List<Vertex> vList){
        //先生成该顶点集合的迪杰斯特拉(Dijkstra)算法表
        Map<String, DijkstraTable> dijkstraTable = DijkstraTable.initialDijkstraTable(vList);

        //我们求第一个顶点到每一个顶点的最短路径,所以我们先把
        //第一个迪杰斯特拉(Dijkstra)算法表的第一个顶点的距离设为0
        //注意,如果想要求出所有顶点到其他顶点的最短路径,复用这个算法,
        //然后将下面的 "vList.get(0).value" 中的 "0" 循环到vList.size()-1即可
        dijkstraTable.get(vList.get(0).value).distance = 0;

        //重要:最短路径算法之迪杰斯特拉(Dijkstra)算法
        while (isNotAllKnown(dijkstraTable)){

            //寻找迪杰斯特拉(Dijkstra)算法表中最小距离的key(known为false中寻找最小)
            String minimumKey = DijkstraTable.getMinimumDijkstraTableKey(dijkstraTable);

            //先标记该顶点为known
            dijkstraTable.get(minimumKey).known = true;

            //获得该顶点
            Vertex currentVertex = dijkstraTable.get(minimumKey).vertex;

            //访问该顶点的所有能够到达的相邻的邻居
            for (Edge item : currentVertex.neighbors){
                //获得当前邻居的value
                String neighborKey = item.neighbor.value;

                //如果该邻居顶点已被访问,则跳过
                if (dijkstraTable.get(neighborKey).known){
                    continue;
                }

                //如果该邻居顶点没有被访问,则先拿出迪杰斯特拉(Dijkstra)算法表里面的距离
                int dijkstraDistance = dijkstraTable.get(minimumKey).distance;

                //拿出到邻居路径的权重
                int neighborDistance = item.weight;

                //如果当前顶点的距离 + 到邻居的权重小于迪杰斯特拉(Dijkstra)算法表里面的距离
                if (dijkstraDistance + neighborDistance < dijkstraTable.get(neighborKey).distance){

                    //需要更新邻居迪杰斯特拉(Dijkstra)算法表里面的距离
                    dijkstraTable.get(neighborKey).distance = dijkstraDistance + neighborDistance;

                    //然后迪杰斯特拉(Dijkstra)算法表里面的上一顶点,更新为当前顶点
                    dijkstraTable.get(neighborKey).previous = currentVertex;
                }
            }
        }

        //上面的while循环结束后,迪杰斯特拉(Dijkstra)算法即终止,解读最终生成的
        //迪杰斯特拉(Dijkstra)算法表,循环打印第一个顶点到每个顶点的最短路径
        for (Vertex item : vList){
            printShortestRouteByDestinationVertex(dijkstraTable, item.value);
        }
    }

    /**
     * 检查图的所有顶点是否全部已访问
     * @param dijkstraTable 待检查的迪杰斯特拉(Dijkstra)算法表
     * @return 是否不是全部已访问
     */
    public static boolean isNotAllKnown(Map<String, DijkstraTable> dijkstraTable){
        //将迪杰斯特拉(Dijkstra)算法表遍历
        for (Map.Entry<String, DijkstraTable> item : dijkstraTable.entrySet()){

            //如果找到了一个known为false的点
            if (!item.getValue().known){
                //证明不是全known,返回true
                return true;
            }
        }

        //遍历完了,还没有找到known为false的点,则证明全部known
        return false;
    }

    /**
     * 根据最终生成的迪杰斯特拉(Dijkstra)算法表,算出起点到当前目的地顶点的最短路径
     * @param dijkstraTable 最终生成的迪杰斯特拉(Dijkstra)算法表
     * @param destinationKey 目的地顶点的key
     */
    public static void printShortestRouteByDestinationVertex(Map<String, DijkstraTable> dijkstraTable, String destinationKey){
        //用于存储路径的集合
        List<String> shortestRoute = new ArrayList<>();

        //将目的地节点装入路径图,从目的地开始倒推到起点
        shortestRoute.add(destinationKey);

        //将目的地节点,作为当前起点
        String currentVertexKey = destinationKey;

        //如果当前节点还有上一个节点
        while (dijkstraTable.get(currentVertexKey).previous != null){

            //获取上一节点
            String previousKey = dijkstraTable.get(currentVertexKey).previous.value;

            //将上一节点装入路径图
            shortestRoute.add(previousKey);

            //上一节点变成当前节点,供下一次循环使用
            currentVertexKey = previousKey;
        }

        //因为我们是反向求路径的,所以将路径图逆序
        Collections.reverse(shortestRoute);

        //打印最短路径图标题
        System.out.print(shortestRoute.get(0) + " 到 "+ shortestRoute.get(shortestRoute.size()-1) + " 的最短路径:");

        //打印最短路径图
        for (int i = 0; i < shortestRoute.size(); i++) {
            if (i == 0){
                System.out.print(shortestRoute.get(i));
            } else {
                System.out.print(" -> " + shortestRoute.get(i));
            }

            //换行
            if (i == shortestRoute.size() - 1){
                System.out.println();
            }
        }
    }

    /**
     * 运行迪杰斯特拉(Dijkstra)算法,求出示例图第一个顶点到所有顶点的的最短路径
     * @param args
     */
    public static void main(String[] args) {
        //构建示例图
        List<Vertex> vList = buildDijkstraGraph();

        //运行迪杰斯特拉(Dijkstra)算法,需要在控制台查看打印结果
        dijkstraAlgorithm(vList);
    }

}

5、运行main方法,控制台输出示例图中V1顶点到其他顶点的最短路径的路线图

V1 到 V1 的最短路径:V1
V1 到 V2 的最短路径:V1 -> V2
V1 到 V3 的最短路径:V1 -> V4 -> V3
V1 到 V4 的最短路径:V1 -> V4
V1 到 V5 的最短路径:V1 -> V4 -> V5
V1 到 V6 的最短路径:V1 -> V4 -> V7 -> V6
V1 到 V7 的最短路径:V1 -> V4 -> V7

6、我们回顾之前的示例图,根据上面给出的最短路径路线图,可以看出程序算出的路径确实是最短的路径,你找不到比上面的路径方案还要短的路径了:
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

算法:通过迪杰斯特拉(Dijkstra)算法,求出图中任意顶点到其他顶点的最短路径 的相关文章

  • msvcr120.dll丢失怎样修复,学这三招就可以修复好

    年前才买的新电脑 今天在打开软件ps软件的时候 电脑就提升msvcr120 dll文件丢失 无法执行此代码 刚刚开始以为是电脑的系统没有装好 经过我一下午时间的研究 原来是电脑msvcr120 dll文件丢失一般都是下载到垃圾软件 导致ms

随机推荐

  • Linux 临时增加环境变量

    增加环境变量 export name value 改变PATH环境变量 export PATH value PATH 这种方法只能临时增加环境变量 关闭shell窗口再打开就失效
  • 如何制作启动盘U盘(纯净版无捆绑)

    如何制作启动盘U盘 纯净版无捆绑 第一步 进入网站下载最新的镜像文件 网站地址 https msdn itellyou cn 如图所示 找到需要下载的展开详细信息 把链接复制下来 打开迅雷软件 会弹出下载链接界面 第二步 下载 rufus
  • CAS和多线程密切相关的东西!

    compare and swap 寄存器A的值和内存M的值进行比较 如果值相同 就把寄存器B和M的值进行交换 更多的时候 不关心寄存器中的数量是啥 更关心内存的数值 变量的值 CAS相当于是把开了新世界的大门 让咱们不加锁就能保证线程安全
  • Linux 搭建 MariaDB Galera Cluster 高可用集群

    MariaDB Galera Cluster 集群介绍 MariaDB Galera Cluster 下文简称MGC集群 是一套在MySQL innodb存储引擎上面实现多主 数据实时同步以及强一致性的关系存储架构 业务层面无需做读写分离工
  • rtklib中的基线约束应对观测条件糟糕的GNSS数据

    文章目录 问题 观测数据质量很差 使用基线约束后和约束前的结果对比 基线约束的原理 问题 观测数据质量很差 最近遇到一个难题 采集了500小时的数据 可是只有大约50 的的数据可以解算 呃 基线长度大约5公里 也算不上长基线 对这批数据进行
  • 串的模式匹配算法-BF算法+KMP算法

    BF算法 include
  • Spring Boot 中的 KafkaTemplate 是什么,原理,如何使用

    Spring Boot 中的 KafkaTemplate 是什么 原理 如何使用 Kafka 是一个流行的分布式消息系统 它可以用于在应用程序之间传递消息 Spring Boot 提供了对 Kafka 的支持 我们可以使用 Spring B
  • org.springframework.beans.factory.UnsatisfiedDependencyException异常问题的解决

    最近学了IDEA和SpringBoot MyBatis了 正所谓学以致用 于是用所学的来做项目 单元测试时报了下面的异常 Caused by org springframework beans factory UnsatisfiedDepe
  • WPF 在XAML中通过控件事件改变另一控件属性

    使用WPF进行开发 很多时候是要注意UI和后台代码的分离 尤其是要改变WinForm中的事件驱动机制 可是近期的开发遇到了这样一个问题 就是当一个控件的事件触发时 改变同级别的另一控件的属性 文字能力实在单薄 还是通过具体例子来说吧 首先
  • 深度学习训练模型的硬件条件

    之前热衷于学习理论知识 目前想跑代码了发现不知道从何下手 自己电脑上搭建的平台基本就是个摆设 因为跑不起来呀 今天我们就来看看想做深度学习应该怎么下手 首先了解下基础知识 1 深度学习用cpu训练和用gpu训练的区别 1 CPU主要用于串行
  • linux 上运行jxbrower出现的问题

    最近做了一个jxbrower抓取微信公众号文章的程序 想着挂在linux上定时运行 布上去却有几个问题这边总结一下 我得服务器是ubuntu16版本的 1 在linux无桌面的版本运行需要用x server运行 2 就是linux的Chro
  • L2-1 包装机PTA

    一种自动包装机的结构如图 1 所示 首先机器中有 N 条轨道 放置了一些物品 轨道下面有一个筐 当某条轨道的按钮被按下时 活塞向左推动 将轨道尽头的一件物品推落筐中 当 0 号按钮被按下时 机械手将抓取筐顶部的一件物品 放到流水线上 图 2
  • qt中drawline函数的参数_Qt--基础图形绘制

    一 基础图形绘制 A Qt图形系统中的关键角色 QPainter Qt中的画家 能够绘制各种基础图形 拥有绘图所需的画笔 画刷 字体 QPaintDevice Qt中的画布 画家的绘图板 所有的QWidget类都继承自QPaintDevic
  • (小白学java)Java 循环结构

    Java中有三种主要的循环结构 while 循环 do while 循环 for 循环 while 循环 和c很像了 不多写了 public class demo public static void main String args in
  • Javascript设计模式-10-迭代器模式

    Javascript设计模式 10 迭代器模式 简介 提供一种方法 顺序访问一个聚合对象中各个元素 而又不需要暴露该方法中的内部表示 迭代器模式可以把迭代的过程从业务逻辑中分离出来 在使用迭代器模式之后 即使不关心对象的内部构造 也可以按顺
  • 个人养老话题

    中国家庭的财富保卫战有三大战场 1 生娃 2 买都市圈里的好房子 3 买好股 好基
  • 【RSA】RSA加密、解密、签名与验证

    前言 最近要做iOS SDK的联网授权 涉及到数据安全验证 因此想到使用RSA进行签名和验证 授权主要流程如下 1 客户方前往我方开放平台注册授权 得到AppId和AppSecret 2 客户方集成SDK 调用Register接口传入App
  • 【数据采集】获取网站数据(二)

    获取网站数据 二 1 常用的数据采集python库 Beautiful Soup https www crummy com software BeautifulSoup bs4 doc zh pyspider http docs pyspi
  • 领域建模概述

    0 概述 在软件工程中 有两个高阶的工作的分别是架构和建模 如果把写代码比喻成施工 那么架构和建模就是设计图纸 相比编码 那么建模的确是对设计经验和抽象能力要求更高的一种技能 本文主要探讨一下对领域建模相关知识的理解 1 什么是领域建模 1
  • 算法:通过迪杰斯特拉(Dijkstra)算法,求出图中任意顶点到其他顶点的最短路径

    请看如下的示例图 该图有 V1 V7 七个顶点 每个顶点之间的距离 路径走向如图所示 假设这是一幅地图 我们很多时候都需要搜路径 比如从家到公司的路线图 也就是说 家是一个点 公司是另一个点 上图的各个点可以想象成分岔路口 当然 如果你不在