数据结构(七)——Dijkasta 、Flyod、马踏棋盘算法

Dijkstra 算法

应用场景-最短路径问题

image-20210824090400570

有 7 个村庄(A, B, C, D, E, F, G) ,现在有六个邮差,从 G 点出发,需要分别把邮件分别送到A, B, C , D, E, F 六个村庄

各个村庄的距离用边线表示(权) ,比如 A – B 距离 5 公里

问:如何计算出 G 村庄到 其它各个村庄的最短距离?

如果从其它点出发到各个点的最短距离又是多少?

迪杰斯特拉(Dijkstra)算法介绍

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

迪杰斯特拉(Dijkstra)算法过程

  1. 设置出发顶点为 v,顶点集合 V{v1,v2,vi…},v 到 V 中各顶点的距离构成距离集合 Dis,Dis{d1,d2,di…},Dis集合记录着 v 到图中各顶点的距离(到自身可以看作 0,v 到 vi 距离对应为 di)

  2. 从 Dis 中选择值最小的 di 并移出 Dis 集合,同时移出 V 集合中对应的顶点 vi,此时的 v 到 vi 即为最短路径

  3. 更新 Dis 集合,更新规则为:比较 v 到 V 集合中顶点的距离值,与 v 通过 vi 到 V 集合中顶点的距离值,保留值较小的一个(同时也应该更新顶点的前驱节点为 vi,表明是通过 vi 到达的)

  4. 递归改过程直到所有节点都被访问过。

image-20210824090744978

代码实现

package Algorithm.Dijkstra;


import java.sql.Struct;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Stack;

/**
 * @author lixiangxiang
 * @description
 * @date 2021/8/21 16:07
 */
public class Dijkstra {
    public static void main(String[] args) {
        char[] vertices = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
        //邻接矩阵
        int[][] matrix = new int[vertices.length][vertices.length];

        //创建 Graph对象

        matrix[0] = new int[]{-1, 5, 7, -1, -1, -1, 2};
        matrix[1] = new int[]{5, -1, -1, 9, -1, -1, 3};
        matrix[2] = new int[]{7, -1, -1, -1, 8, -1, -1};
        matrix[3] = new int[]{-1, 9, -1, -1, -1, 4, -1};
        matrix[4] = new int[]{-1, -1, 8, -1, -1, 5, 4};
        matrix[5] = new int[]{-1, -1, -1, 4, 5, -1, 6};
        matrix[6] = new int[]{2, 3, -1, -1, 4, 6, -1};
        int index = 4;
        Graph graph = new Graph(vertices,matrix,index);
        graph.dsj(index);
        graph.print();
    }


}

class Graph {
    //顶点
    char[] vertices;
    // 记录起点到其他顶点的距离
    int[] dis;
    // 记录已经访问过的节点
    private boolean[] visited;
    // 每个下标对应的值为前一个顶点下标, 会动态更新
    int[] pre_visited;
    // 邻接矩阵储存表
    private int[][] matrix;

    // 开始顶点
    private int index;
    public Graph(char[] vertices, int[][] matrix,int index) {
        this.vertices = vertices;
        this.matrix = matrix;
        this.index = index;
        visited = new boolean[vertices.length];
        pre_visited = new int[vertices.length];
        dis = new int[vertices.length];
        this.visited[index] = true;
        Arrays.fill(dis,Integer.MAX_VALUE);
        dis[index] = 0;
    }

    /**
     * 更新其他顶点到index顶点的距离 以及 设置其他顶点前一顶点为 index
     *
     * @param index
     */
    public void dsj(int index) {
        int len = 0;
       // 如果已经没有未访问过的退出递归
        if (index == -1) {
            return;
        }
        // 找到未访问过的最小下标
        for (int i = 0; i < matrix[index].length; i++) {
            if (matrix[index][i] !=  -1) {
                // 计算 index 距离 i的距离,是一个逐渐累加的过程。顶点到index顶点的距离+index到j顶点的距离之和
                len = dis[index] + matrix[index][i];
                // 如果当前节点未访问过 则拿当前的这条路径的距离 与数组中index 到 i的距离相比较,取最小值
                if (!visited[i] && len < dis[i]) {
                    dis[i] = len;
                    //设置i的前趋节点为index
                    pre_visited[i] = index;
                }
            }

        }
        // 设置当前节点已经访问过
        visited[index] = true;
        int min = getMinUnVisited();
        dsj(min);
    }


    /**
     * 获取dis中最小一个被访问过的节点
     */
    public int getMinUnVisited() {
        int min = Integer.MAX_VALUE;
        int index = -1;
        for(int i = 0; i < visited.length; i++) {
            if (!visited[i] && min > dis[i]) {
                min = dis[i];
                index = i;
            }
        }
        return index;
    }

    public void print() {
        for (int i = 0; i < dis.length; i++) {
            if (i != index) {
                System.out.println(vertices[index] +"到"+vertices[i]+"的最短距离为"+ dis[i]);
                System.out.print("路线为:");
                int pre = i;
                Stack<Integer> stack = new Stack<>();
                while (pre != index) {
                    pre = pre_visited[pre];
                    stack.push(pre);
                }
                while (!stack.isEmpty()) {
                    System.out.print(vertices[stack.pop()]+"->");
                }
                System.out.print(vertices[i]);
                System.out.println();
            }
        }
    }

}

弗洛伊德算法

弗洛伊德算法介绍

  1. 弗洛伊德算法(Floyd)计算图中各个顶点之间的最短路径

  2. 迪杰斯特拉算法用于计算图中某一个顶点到其他顶点的最短路径。

  3. 弗洛伊德算法 与 迪杰斯特拉算法区别

    迪杰斯特拉算法通过选定的被访问顶点,求出从出发访问顶点到其他顶点的最短路径

    弗洛伊德算法中每一个顶点都是出发访问点,所以需要将每一个顶点看做被访问顶点,求出从每一个顶点到其他顶点的最短路径

弗洛伊德(Floyd)算法图解分析

  1. 设置顶点 vi 到顶点 vk 的最短路径已知为 Lik,顶点 vk 到 vj 的最短路径已知为 Lkj,顶点 vi 到 vj 的路径为 Lij, 则 vi 到 vj 的最短路径为:min((Lik+Lkj),Lij),vk 的取值为图中所有顶点,则可获得 vi 到 vj 的最短路径

  2. 至于 vi 到 vk 的最短路径 Lik 或者 vk 到 vj 的最短路径 Lkj,是以同样的方式获得

image-20210824195705448

代码思路:使用三层for循环对初始距离表 进行遍历,第一层为中间节点的下标,第二层为起始节点的下标,第三层为终点节点的下标。

初始的距离表和前趋节点表

image-20210824203245695

第一次遍历时,以A作为中间节点,获取所有以A为中间顶点到达终点的情况,更新距离表和前趋关系表。

如 BAC(12)、CAG(9)

以 A 顶点作为中间顶点是,B->A->C 的距离由 N->12,同理 C 到 B;C->A->G 的距离由 N->9,更新后的表:

image-20210824203155004

当以A为中间顶点的路径遍历完之后再以其他顶点为中间节点继续遍历,计算结束。

代码实现

package Algorithm.Floyd;

import java.util.Arrays;
import java.util.Stack;

/**
 * @author lixiangxiang
 * @description Floyd算法
 * @date 2021/8/24 11:47
 */
public class FloydAlgorithm {

    public static void main(String[] args) {
        char[] vertex = { 'A', 'B', 'C', 'D', 'E', 'F', 'G' };
        int[][] matrix = new int[vertex.length][vertex.length];
        final int N = 65535;
        matrix[0] = new int[] { 0, 5, 7, N, N, N, 2 };
        matrix[1] = new int[] { 5, 0, N, 9, N, N, 3 };
        matrix[2] = new int[] { 7, N, 0, N, 8, N, N };
        matrix[3] = new int[] { N, 9, N, 0, N, 4, N };
        matrix[4] = new int[] { N, N, 8, N, 0, 5, 4 };
        matrix[5] = new int[] { N, N, N, 4, 5, 0, 6 };
        matrix[6] = new int[] { 2, 3, N, N, 4, 6, 0 };
        //创建 Graph 对象
        Graph graph = new Graph(vertex, matrix);
        graph.floyd();
        System.out.println(Arrays.deepToString(graph.dis));
        System.out.println(Arrays.deepToString(graph.pre));
        graph.print();

    }


}


class Graph {
    //顶点
    char[] vertices;
    // 记录前趋顶点
    int[][] pre;
    // 记录距离
    int[][] dis;

    public Graph(char[] vertices, int[][] matrix) {
        this.vertices = vertices;
        this.dis = matrix;
        this.pre = new int[vertices.length][vertices.length];
        // 对pre数组初始化, 注意存放的是前驱顶点的下标
        for (int i = 0; i < vertices.length; i++) {
            Arrays.fill(pre[i], i);
        }

    }

    /**
     * 更新其他顶点到index顶点的距离 以及 设置其他顶点前一顶点为 index
     *
     * @param
     */
    public void floyd() {
        // 记录距离
        int len = 0;
        for (int k = 0; k < dis.length; k++) {
            for (int i = 0; i < dis.length; i++) {
                for (int j = 0; j < dis.length; j++) {
                    // 求出从i 顶点出发,经过 k中间顶点,到达 j 顶点的距离
                    len = dis[i][k] + dis[k][j];
                    // 如果比之前记录的距离小则更新。
                    if (len < dis[i][j]) {
                        // 更新距离
                        dis[i][j] = len;
                        // 更新前驱节点
                        pre[i][j] = pre[k][j];
                    }
                }
            }
        }
    }

    public void print() {
        for (int index = 0; index < dis.length; index++) {
            System.out.println("以"+vertices[index]+"为起点");
            for (int i = 0; i < dis[index].length; i++) {
                if (i != index) {
                    System.out.println(vertices[index] +"到"+vertices[i]+"的最短距离为"+ dis[index][i]);
                    System.out.print("路线为:");
                    int start = i;
                    Stack<Integer> stack = new Stack<>();
                    while (start != index) {
                        start = pre[index][start];
                        stack.push(start);
                    }
                    while (!stack.isEmpty()) {
                        System.out.print(vertices[stack.pop()]+"->");
                    }
                    System.out.print(vertices[i]);
                    System.out.println();
                }
            }
            System.out.println("=====================================");
        }
    }


}

马踏棋盘算法

马踏棋盘算法介绍

  1. 马踏棋盘算法也被称为骑士周游问题

  2. 将马随机放在国际象棋的 8×8 棋盘 Board[0~7][0~7]的某个方格中,马按走棋规则(马走日字)进行移动。要求每个方格只进入一次,走遍棋盘上全部 64 个方格

image-20210824203927036

马踏棋盘算法实现

  1. 马踏棋盘问题(骑士周游问题)实际上是图的深度优先搜索(DFS)的应用。
  1. 使用回溯来解决,马每走一步就去寻找下面能走的路径,一直往下走,当无路可走时判断是否走满棋盘,如果没有说明路径不合适,进行回溯,重新选择一条路继续走。

回溯法代码实现

package Algorithm.horse;

import java.awt.*;
import java.util.ArrayList;
import java.util.Comparator;

/**
 * @author lixiangxiang
 * @description 马踏棋盘算法
 * @date 2021/8/24 15:18
 */
public class Horse {
    private static int X; // 棋盘的列数
    private static int Y; // 棋盘的行数
    //创建一个数组,标记棋盘的各个位置是否被访问过
    private static boolean[] visited;
    //标记是否棋盘的所有位置都被访问
    private static boolean finished;



    public static void main(String[] args) {
        Horse.X = 6;
        Horse.Y = 6;
        Horse.visited = new boolean[X * Y];
        // 创建棋盘
        int[][] chessboard = new int[X][Y];
        long start = System.currentTimeMillis();
        traversalChessboard(chessboard,0,5,1);
        long end = System.currentTimeMillis();
        System.out.println("共耗时"+(end - start) +"ms");

        //输出棋盘的最后情况
        for(int[] rows : chessboard) {
            for(int step: rows) {
                System.out.print(step + "\t");
            }
            System.out.println();
        }
    }


    /**
     * description:
     *
     * @param chessboard 棋盘
     * @param row        当前行
     * @param column     当前列
     * @param step       步数
     * @return void
     * @author: lixiangxiang
     * @date 2021/8/24 15:43
     */
    public static void traversalChessboard(int[][] chessboard, int row, int column, int step) {
        // 记录步数
        chessboard[row][column] = step;
        // 标记该位置已经被访问
        visited[row * X + column] = true;
        // 根据当前节点的下下一步能走的个数对nexts排序(贪心算法)
        nexts.sort(Comparator.comparingInt(o -> next(o).size()));
        while (!nexts.isEmpty()) {
            // 取出下一个走的位置
            Point point = nexts.remove(0);
            // 如果这个位置还未走过
            if (!visited[point.y * X + point.x]) {
                // 对该位置进行递归
                traversalChessboard(chessboard, point.y, point.x, step + 1);
            }
        }
        // 判断运算是否完成,如果未完成则进行回溯
        if (step < X * Y && !finished) {
            // 未完成将棋盘所有走过的位置 置0
            chessboard[row][column] = 0;
            // 将已经访问过的节点
            visited[row * X + column] = false;
            step = 1;
        } else {
            finished = true;
        }
    }


    /**
     * description: 获取下一步可走的位置
     *
     * @param curPoint 当前点的坐标
     * @return java.util.ArrayList<java.awt.Point>
     * @author: lixiangxiang
     * @date 2021/8/24 15:42
     */
    public static ArrayList<Point> next(Point curPoint) {
        //创建一个ArrayList
        ArrayList<Point> ps = new ArrayList<>();
        // 创建坐标
        Point p1 = new Point();
        //判断马儿是否可以走5这个位置
        if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y - 1) >= 0) {
            ps.add(new Point(p1));
        }
        //判断马儿是否可以走6这个位置
        if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y - 2) >= 0) {
            ps.add(new Point(p1));
        }
        //判断马儿是否可以走7这个位置
        if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y - 2) >= 0) {
            ps.add(new Point(p1));
        }
        //判断马儿是否可以走0这个位置
        if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y - 1) >= 0) {
            ps.add(new Point(p1));
        }
        //判断马儿是否可以走1这个位置
        if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y + 1) < Y) {
            ps.add(new Point(p1));
        }
        //判断马儿是否可以走2这个位置
        if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y + 2) < Y) {
            ps.add(new Point(p1));
        }
        //判断马儿是否可以走3这个位置
        if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y + 2) < Y) {
            ps.add(new Point(p1));
        }
        //判断马儿是否可以走4这个位置
        if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y + 1) < Y) {
            ps.add(new Point(p1));
        }
        return ps;
    }
}

使用贪心算法对算法进行优化

优化方法很简单,我们只需要在选择马下步走的位置时,选择其下下部可走位置最少的即可,即根据下下步可走位置来对下步可走位置进行排序,每步都选择下下步可走路径最少的位置走。这样可大大减少回溯次数。

  public static void traversalChessboard(int[][] chessboard, int row, int column, int step) {
        // 记录步数
        chessboard[row][column] = step;
        // 标记该位置已经被访问
        visited[row * X + column] = true;
        // 获取当前位置可以走的下一个位置的集合
        ArrayList<Point> nexts = next(new Point(column, row));
        // 根据当前节点的下下一步能走的个数对nexts排序(贪心算法)
        nexts.sort(Comparator.comparingInt(o -> next(o).size()));
        while (!nexts.isEmpty()) {
            // 取出下一个走的位置
            Point point = nexts.remove(0);
            // 如果这个位置还未走过
            if (!visited[point.y * X + point.x]) {
                // 对该位置进行递归
                traversalChessboard(chessboard, point.y, point.x, step + 1);
            }
        }
        // 判断运算是否完成,如果未完成则进行回溯
        if (step < X * Y && !finished) {
            // 未完成将棋盘所有走过的位置 置0
            chessboard[row][column] = 0;
            // 将已经访问过的节点
            visited[row * X + column] = false;
            step = 1;
        } else {
            finished = true;
        }
    }

我们通过测试运算速度可以发现时间消耗大大减少

未加贪心:

image-20210824205819860

使用贪心算法之后

image-20210824205856497

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值