经典算法 - 最短路径问题与Dijkstra、Floyd算法

图的最短路径问题

前面介绍了图的基本知识:数据结构 - 图与深度优先、广度优先遍历

现有一个图:有7个顶点{'A','B','C','D','E','F','G'},顶点间长度为权值

在这里插入图片描述

假设起点为G,现要求G点到各顶点的最短路径


迪杰斯特拉算法(Dijkstra)

Dijkstra算法以起始点为中心向外层层扩展(广度优先遍历),采用贪心算法的策略,每次遍历到始点距离最近且未访问过的顶点的邻接节点,直到扩展到终点为止

百度百科:
在这里插入图片描述

核心思路:

  • 设置3个数组:alreadyArr记录以访问的顶点;preVisited记录各个顶点前驱结点;dis记录起点到各顶点的距离
  • 从起点1开始广度优先遍历(每次遍历都更新3个数组,如顶点3的前驱结点为1,距离为9…后续遍历如果有更小值会覆盖);找到权值最小的边<1,2>=7,即下一个访问的顶点2,设置顶点2的前驱结点为1,距离为7
  • 从顶点2开始广度遍历,重复上一步,到顶点3的距离为7+10=17>9,不更新preVisited和dis,选中顶点3,直到所有顶点都被访问,dis数组就是最终结果

Java代码实现Dijkstra算法

  • 算法创建了3个对象:Graph图对象、VisitedVertex已访问顶点的集合,保存了3个数组;DijkstraAlgorithm主类
  • VisitedVertex对象:初始化时需要设置dis自身为0,其余全为最大值(表示不连通),且设置起点被访问;updateArr方法为根据边权值选择下一访问点,访问alreadyArr集合与之对应的dis集合数据,找到最小值
  • Graph图:update方法更新index下标顶点到周围顶点的距离dis数组和周围顶点的前驱顶点preVisited数组;dijkstra方法,从起点开始调用update方法直到所有顶点被访问
package com.company.十种算法.dijkstra;

import java.util.Arrays;

/**
 * Author : zfk
 * Data : 17:15
 */
public class DijkstraAlgorithm {

    //用INF表示两个顶点不能连通
    private static final int INF = 65535;

    public static void main(String[] args) {


        char[] vertex = new char[]{'A','B','C','D','E','F','G'};
        int[][] matrix = {
                {INF,5,7,INF,INF,INF,2},
                {5,INF,INF,9,INF,INF,3},
                {7,INF,INF,INF,8,INF,INF},
                {INF,9,INF,INF,INF,4,INF},
                {INF,INF,8,INF,INF,5,4},
                {INF,INF,INF,4,5,INF,6},
                {2,3,INF,INF,4,6,INF}

        };
        Graph graph = new Graph(vertex, matrix);

        graph.showGraph();


        graph.dijkstra(6);
        graph.showDij();
    }

}


class Graph{
    //顶点数组
    private char[] vertex;
    //邻接矩阵
    private int[][] matrix;
    //表示已经访问的顶点的集合
    private VisitedVertex visitedVertex;

    public Graph(char[] vertex, int[][] matrix) {
        this.vertex = vertex;
        this.matrix = matrix;
    }
    public void showDij(){
        visitedVertex.show();
    }

    public void showGraph(){
        for (int[] link : matrix){
            System.out.println(Arrays.toString(link));
        }
    }
    /**
     * dijkstra算法实现
     * @param index 出发顶点对应的下标
     */
    public void dijkstra(int index){
        visitedVertex = new VisitedVertex(vertex.length, index);
        //更新index顶点到周围顶点的距离和前驱
        update(index);

        for (int j = 1;j < vertex.length;j++){
            //选择并返回新的访问结点
            index = visitedVertex.updateArr();
            //更新index顶点到周围顶点的距离和前驱
            update(index);
        }
    }

    //更新index下标顶点到周围顶点的距离和周围顶点的前驱顶点
    private void update(int index){
        int len = 0;
        //根据遍历我们的邻接矩阵matrix
        for (int j = 0;j < matrix[index].length;j++){
            //len 表示出发顶点到index顶点距离+从index顶点到j顶点的距离和
            len = visitedVertex.getDis(index) + matrix[index][j];
            //如果j顶点没有被访问过并且len 小于出发顶点到j顶点的距离,就需要更新
            if (!visitedVertex.in(j) && len < visitedVertex.getDis(j)){
                //更新顶点j的前驱为index
                visitedVertex.updatePre(j,index);
                //更新出发顶点到j顶点的距离
                visitedVertex.updateDis(j,len);
            }
        }
    }

}

//已访问顶点的集合
class VisitedVertex{
    //记录以访问的顶点 - 动态更新
    public int[] alreadyArr;
    //记录前驱结点,每个值为前一个顶点下标 - 动态更新
    public int[] preVisited;
    //记录出发顶点到其他所有顶点的距离,求的最短距离就会存在dis
    public int[] dis;

    /**
     *
     * @param length 顶点的格式
     * @param index 出发顶点对应的下标
     */
    public VisitedVertex(int length,int index) {
        this.alreadyArr = new int[length];
        this.preVisited = new int[length];
        this.dis = new int[length];
        //全部置为最大值
        Arrays.fill(dis,65535);
        //设置出发顶点被访问过
        this.alreadyArr[index] = 1;
        //自身置0
        this.dis[index] = 0;
    }

    /**
     * 判断index顶点是否被访问过
     * @param index
     * @return 访问过返回true,否则为false
     */
    public boolean in(int index){
        return alreadyArr[index] == 1;
    }

    /**
     * 更新出发顶点到index顶点的距离
     * @param index
     * @param len
     */
    public void updateDis(int index,int len){
        dis[index] = len;
    }

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

    /**
     * 返回出发顶点到index顶点的距离
     * @param index
     */
    public int getDis(int index){
        return dis[index];
    }


    /**
     * 继续选择并返回新的访问顶点,G点访问完后,以A点作为新的访问顶点
     * @return
     */
    public int updateArr(){
        int min = 65535, index = 0;
        for (int i = 0;i < alreadyArr.length;i++){
            if (alreadyArr[i] == 0 && dis[i] < min){
                min = dis[i];
                index = i;
            }
        }
        //更新index顶点被访问过
        alreadyArr[index] = 1;
        return index;

    }

    /**
     * 显示最后的结果,即将三个数组的情况输出
     */
    public void  show(){

        System.out.println("==============已访问的顶点===============");
        //输出alreadyArr
        for (int i : alreadyArr){
            System.out.print(i+ " ");
        }
        System.out.println();
        System.out.println("===============前驱顶点==============");
        //输出preVisited
        for (int i : preVisited){
            System.out.print(i+ " ");
        }
        System.out.println();
        System.out.println("==============顶点间最短距离===============");
        //输出dis
        for (int i : dis){
            System.out.print(i+ " ");
        }
        System.out.println();
        char[] vertex = new char[]{'A','B','C','D','E','F','G'};
        int count = 0;
        for (int i : dis){
            if (i != 65535){
                System.out.print(vertex[count] + "( " + i + " )");
            }else {
                System.out.println(" N " );
            }
            count++;
        }

    }
}

最终结果:

在这里插入图片描述


弗洛伊德算法(Floyd)

同为最短路径问题的算法,floyd算法是得到所有顶点到其他顶点的最短路径(当然Dijkstra也可以)

floyd算法的核心思想是三层for循环与
状态转移方程dis[i][j] = min{dis[i][k] + dis[k][j],dis[i][j]}即找到权值最小边

在这里插入图片描述

设置好两个二维数组:dis保存各顶点出发到其他顶点的距离;pre保存顶点的前驱结点
初始化时dis数组就是图的邻接矩阵,设置结点的前驱结点为自身
INF表示结点间不连通,顶点到自身的距离为0

        int[][] matrix = {
                {0,5,7,INF,INF,INF,2},
                {5,0,INF,9,INF,INF,3},
                {7,INF,0,INF,8,INF,INF},
                {INF,9,INF,0,INF,4,INF},
                {INF,INF,8,INF,0,5,4},
                {INF,INF,INF,4,5,0,6},
                {2,3,INF,INF,4,6,0}
        };

查找最短路径一般都是直连与间接连通的长度比较:min{dis[i][k] + dis[k][j],dis[i][j]},如AB的最短距离是<A,B>还是<A,G>+<G,B>呢?

所有我们引入中间结点,例如,以A为中间结点,所有经过A点的两顶点间的最短路径就是min{dis[i][0] + dis[0][j],dis[i][j]}

  1. 起点为A,终点为{'A','B','C','D','E','F','G'}

与A连通的结点B、C、G,更新dis数组和pre数组;
不连通的设置一个最大值INF表示即可

  1. 起点为B,终点为{'A','B','C','D','E','F','G'}]
    实际上都大于以A为起点的距离,所以不用更新dis数组和pre数组

在这里插入图片描述

  1. 继续循环起点为{C,D,E,F,G},找到所有经过顶点A的两顶点的最短路径

  2. 然后就是循环中间结点{'A','B','C','D','E','F','G'},类似与穷举法,找出了所有可能,最终dis就是所有顶点到其他顶点的最短距离


Java代码实现floyd算法

floyd算法重点就在与三个for循环

package com.company.十种算法.floyd;

import java.util.Arrays;

/**
 * Author : zfk
 * Data : 16:51
 * 弗洛伊德算法 : 最短路径问题(所有结点)
 */
public class FloydAlgorithm {

    //用INF表示两个顶点不能连通
    private static final int INF = 65535;

    public static void main(String[] args) {
        char[] vertex = new char[]{'A','B','C','D','E','F','G'};
        int[][] matrix = {
                {0,5,7,INF,INF,INF,2},
                {5,0,INF,9,INF,INF,3},
                {7,INF,0,INF,8,INF,INF},
                {INF,9,INF,0,INF,4,INF},
                {INF,INF,8,INF,0,5,4},
                {INF,INF,INF,4,5,0,6},
                {2,3,INF,INF,4,6,0}
        };

        Graph graph = new Graph(matrix, vertex);
        graph.floyd();
        graph.show();

    }
}

//图对象
class Graph{
    //图中顶点数组
    private char[] vertex;
    //保存各顶点出发到其他顶点的距离,最后结果也是保存在该数组
    private int[][] dis;
    //保存到达目标顶点的前驱结点
    private int[][] pre;

    /**
     * 构造器
     * @param matrix 邻接矩阵
     * @param vertex 顶点数组
     */
    public Graph(int[][] matrix,char[] vertex){
        int length = vertex.length;
        this.vertex = vertex;
        this.dis = matrix;
        this.pre = new int[length][length];
        //对pre初始化,最开始时各顶点的前驱为自身
        // 且该数组存放的是顶点的下标 'A' - 0
        for (int i =0;i < length;i++){
            Arrays.fill(pre[i],i);
        }

    }

    //显示pre数组合dis数组
    public void show(){
        System.out.println("==========pre数组============");
        for (int k = 0;k < dis.length;k++){
            for (int j = 0;j < dis.length;j++){
                System.out.print(vertex[pre[k][j]] + " ");
            }
            System.out.println();
        }

        System.out.println("==========dis数组============");
        for (int k = 0;k < dis.length;k++){
            for (int j = 0;j < dis.length;j++){
                System.out.printf("%2d\t",dis[k][j]);
            }
            System.out.println();
        }

    }

    //弗洛伊德算法
    public void floyd(){
        //两顶点的距离
        int len = 0;
        int length = dis.length;

        //对中间顶点数组变量,k为中间顶点数组的下标,{'A','B','C','D','E','F','G'}
        for (int k = 0;k < length;k++){
            //从i顶点出发,{'A','B','C','D','E','F','G'}
            for (int i = 0;i < length;i++){
                //j顶点为终点,{'A','B','C','D','E','F','G'}
                for (int j = 0;j < length;j++){
                    //从i顶点出发经过k中间顶点,到达j顶点的距离
                    len = dis[i][k] + dis[k][j];
                    //len小于两顶点直连的距离,就更新
                    if (len < dis[i][j]){
                        dis[i][j] = len;
                        //设置前驱结点
                        pre[i][j] = pre[k][j];
                    }
                }

            }

        }
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值