Java最短路径:Dijkstra(迪杰斯特拉)算法、最短路径问题、思路分析、代码实现


Dijkstra(迪杰斯特拉)算法

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


案例:最短路径问题

在这里插入图片描述

这里有6个村庄,分别是A、B、C、D、E、F,从G点出发需要将邮件分别送到这6个村庄,各个村庄有不同的路线,路线上都标有公里数,计算G村庄到各个村庄的最短距离?如果从其他点出发,各个点的最短距离又是多少?


. 思路分析

整体思路:

  • 选择G为起始点,那么G就设为已访问,用一个记录路径的数组存储每个节点到达初始节点G的路径,首先以G为起始点,与G有连通的节点,就将对应的节点的路径填入数组,并将这些可连通的节点的前驱节点设置为G。然后再从其中选择一个没有访问的路径最短的节点作为下一个起点,即A点。现在就是走了G-A这条路,A设置为已访问,然后获取没有访问过的且与A有连通的节点的路径,然后这路径也会与该连通的节点的原始路径比较,小的就赋值给这个连通的节点,并将这些可连通的节点的前驱节点设置为A。依次类推…

案例具体思路:

有数组 :

int[] isVisitedVertex // 记录访问的节点,1为已访问,0为没访问

int[] vertexDis // 记录从G点到每个点的最短路径距离,默认值存入一个比较大的数65535表示无穷

int[] preVertex // 记录每个节点的前驱节点

A、B、C、D、E、F、G这几个点对应的索引下标就是0、1、2、3、4、5、6

在这里插入图片描述

  • 首先看图1,从G点出发,将G点设置为已访问,由于G点没有前驱节点,所以它的前驱节点可以设置为它自己,即preVertex[6]=6,以G点进行广度优先遍历,满足可连接且没有被访问过的点,得到了A、B、E、F这四个点可以与G连通,并且路径分别是2、3、4、6,存入到对应索引下的vertexDis数组,即vertexDis[0]=2; vertexDis[1]=3; vertexDis[4]=4; vertexDis[5]=6,这四个节点的前驱节点更新为G的索引。图2,蓝色表示的已存入到vertexDis数组到达这四个顶点的距离。在没有被访问的路径下,选择一个已有路径最小的节点,所以会选择到A。

在这里插入图片描述

  • 那么A就成为新的起点,将A标记已访问,以A点进行广度优先遍历,满足可连接且没有被访问过的点,得到了C、B两个点,路径分别是7、5。比较B已存在的距离是3,那么从A点到达B点,长度为A已走的距离加上A到B的距离即7,大于B已存在的距离3,所以B的距离不会发生变化;C已存在的距离是默认值65535,此时从G到C的距离是A已走的距离加上A到C的距离即9,小于C已存在的距离,所以C的最新距离就是9,因此C的前驱节点就变成了A,即preVertex[2]=0,并将距离更新到vertexDis数组中。因此得到了图4的结果。在没有被访问的路径下,选择一个已有路径最小的节点,所以会选择到B

在这里插入图片描述

  • 重复第二步操作。得到图6。在没有被访问的路径下,选择一个已有路径最小的节点,所以会选择到E

在这里插入图片描述

  • 重复第二步操作。得到图6。在没有被访问的路径下,选择一个已有路径最小的节点,所以会选择到F

在这里插入图片描述

  • 重复第二步操作。得到图6。在没有被访问的路径下,选择一个已有路径最小的节点,所以会选择到C

在这里插入图片描述

  • 重复第二步操作。得到图6。在没有被访问的路径下,选择一个已有路径最小的节点,所以会选择到D,最终D会被标记已访问。最终从G到每个点的最短路径都出来了。

. 代码实现

/**
 * Created with IntelliJ IDEA
 * User: heroC
 * Date: 2020/6/13
 * Time: 12:44
 * Description:迪杰斯特拉算法
 * 求某个顶点到达各个顶点的最短路径?
 * 1,创建一个图,构建顶点数据和邻接矩阵
 * 2,构建一个访问类,有3个主要的数组,分别是记录已被访问的数组、记录前驱节点的数组、已走的记录路径值
 * 3,创建一个更新路径的方法,即选取一个最小的当前点到下一个没有访问的点的距离
 * 4,创建一个获取没有被访问过的最小路径的新的节点为起点的方法
 * Version: V1.0
 */
public class Dijkstra {
    // NIF常量表示没有该条路径
    private static final int NIF = 65535;

    public static void main(String[] args) {
        char vertexData[] = {'A','B','C','D','E','F','G'};
        int weight[][] = {
                {NIF, 5, 7, NIF, NIF, NIF, 2},
                {5, NIF, NIF, 9, NIF, NIF, 3},
                {7, NIF, NIF, NIF, 8, NIF, NIF},
                {NIF, NIF, NIF, 4, 5, NIF, 6},
                {NIF, NIF, 8, NIF, NIF, 5, 4},
                {NIF, NIF, NIF, 4, 5, NIF, 6},
                {2, 3, NIF, NIF, 4, 6, NIF}
        };
        // 迪杰斯特拉算法
        dijkstraAlgorithm(vertexData,weight,6);
    }

    /**
     * 迪杰斯特拉算法
     * @param vertexData 顶点数据
     * @param weight 邻接矩阵,权的关系
     * @param start 起始顶点的索引
     */
    public static void dijkstraAlgorithm(char[] vertexData, int[][] weight, int start){
        // 构建一个图
        DijGraph dijGraph = new DijGraph(vertexData, weight);
        // 将图传递给Visited类,做处理
        Visited visited = new Visited(start, dijGraph);
        // 更新以索引为6(G)作为起点的与索引为6这个起点(G)连通的节点的路径距离
        visited.updateDis(start);
        // 由于起点已经排除,所以只需要判断剩下的节点即可
        for (int i = 1; i < dijGraph.vertexData.length; i++) {
            // 获取新的最短路径的节点为新的起点(因为一个节点走过了,那么下一个节点就成为新的起点)
            int index = visited.getNewMinRoutVertex();
            // 更新与新的起点连通的节点的距离,选择最小的作为连通点的距离
            visited.updateDis(index);
        }

        // 展示从起始点G到每个节点的最短路径
        visited.showVertexDis();
    }
}


class Visited{
    // 用于记录已访问的点
    private int[] isVisitedVertex;
    // 用于记录起始点到达该点的距离
    private int[] vertexDis;
    // 用于记录该点的前驱节点,记录前驱节点的作用就是可以遍历得到行走的轨迹
    private int[] preVertex;
    // NIF表示两点不可连通
    private static final int NIF = 65535;
    private DijGraph graph;
    // 记录起始顶点索引
    private int startVertexIndex;

    /**
     * 构造函数,对3个数组的初始化,以及接收图
     * @param index 起始点的索引
     * @param graph 图
     */
    public Visited(int index, DijGraph graph) {
        int len = graph.vertexData.length;

        this.isVisitedVertex = new int[len];
        isVisitedVertex[index] = 1; // 起始点设置为已访问,1表示已访问,0表示未被访问

        this.vertexDis = new int[len];
        Arrays.fill(vertexDis,NIF); // 初始距离都是NIF
        vertexDis[index] = 0; // 起始点的距离,也就是G到G的距离就是0

        this.preVertex = new int[len];
        preVertex[index] = index; // 设置起始点的前驱节点的索引,就是他自己

        this.startVertexIndex = index;

        this.graph = graph;
    }

    /**
     * 获得index索引节点的路径
     * @param index
     * @return
     */
    public int getDis(int index){
        return vertexDis[index];
    }

    /**
     * 判断该索引的节点是否已被访问
     * @param index
     * @return
     */
    public boolean getIsVisitedVertex(int index){
        return isVisitedVertex[index]==1;
    }

    /**
     * 更新每个节点到起始节点的路径长度
     * @param index 当前起点
     */
    public void updateDis(int index){
        for (int j = 0; j < graph.weight[index].length; j++) {
            // 类似广度优先搜索遍历,获取index这个节点与其连通的节点之间的距离加上index这个节点已经走过的距离
            int len = getDis(index) + graph.weight[index][j];
            // 如果len小于连通的这个节点已经存在的距离,那么说明走到j这个节点的距离最小的是len这个距离
            // 并且j这个节点没有被访问过的话,就应该将这个节点的距离赋值为最小的len
            // j这个节点的前驱节点就是index这个节点了
            if (!getIsVisitedVertex(j) && len < getDis(j)){
                vertexDis[j] = len;
                preVertex[j] = index;
            }
        }
    }

    /**
     * 获取没有访问过的节点中,已走的最短距离的那个节点的索引,
     * 将其设置为已访问,并返回这个节点,作为新的起点
     * @return
     */
    public int getNewMinRoutVertex(){
        int min = NIF;
        int index = 0;
        for (int i = 0; i < isVisitedVertex.length; i++) {
            if (isVisitedVertex[i]==0 &&  vertexDis[i] < min){
                min = vertexDis[i];
                index = i;
            }
        }
        isVisitedVertex[index] = 1;
        return index;
    }

    // 遍历记录到达每个节点最短路径数组,以及记录的前驱节点数组
    public void showVertexDis(){
        for (int i = 0; i < vertexDis.length; i++) {
            if (vertexDis[i]!=NIF){
                System.out.println(graph.vertexData[i] + " : " +vertexDis[i]);
            }else {
                System.out.println("NIF");
            }
        }

        System.out.println("具体行走路线:");
        Set<Character> set = new LinkedHashSet<>();
        Stack<Character> stack = new Stack<>();
        for (int i = 0; i < preVertex.length; i++) {
            int temp = i;
            set.add(graph.vertexData[i]);
            do{
                temp = preVertex[temp];
                set.add(graph.vertexData[temp]);
            }while (preVertex[temp]!=startVertexIndex);
            set.add(graph.vertexData[preVertex[temp]]);
            for (Character character : set) {
                stack.add(character);
            }
            while (!stack.isEmpty()){
                System.out.print(stack.pop()+" ");
            }
            set.clear();
            System.out.println(": "+vertexDis[i]);
        }
    }
}

class DijGraph{
    char[] vertexData; // 存储图中顶点的数据
    int[][] weight; // 邻接矩阵,即权路径

    public DijGraph(char[] vertexData, int[][] weight) {
        this.vertexData = new char[vertexData.length];
        for (int i = 0; i < vertexData.length; i++) {
            this.vertexData[i] = vertexData[i];
        }
        this.weight = new int[vertexData.length][vertexData.length];
        for (int i = 0; i < weight.length; i++) {
            for (int j = 0; j < weight[i].length; j++) {
                this.weight[i][j] = weight[i][j];
            }
        }
    }
}

结果:

A : 2
B : 3
C : 9
D : 10
E : 4
F : 6
G : 0
具体行走路线:
G A : 2
G B : 3
G A C : 9
G F D : 10
G E : 4
G F : 6
G : 0
  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值