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

22 篇文章 1 订阅
20 篇文章 0 订阅

请看如下的示例图,该图有 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、我们回顾之前的示例图,根据上面给出的最短路径路线图,可以看出程序算出的路径确实是最短的路径,你找不到比上面的路径方案还要短的路径了:
在这里插入图片描述

  • 5
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值