首页 > 其他分享 >骑士周游问题

骑士周游问题

时间:2023-05-29 14:01:48浏览次数:37  
标签:ps 周游 p1 Point int 问题 curPoint new 骑士

1. 算法优化意义   904

1.算法是程序的灵魂,为什么有些程序可以在海量数据计算时,依然保持高速计算?

2.在Unix下开发服务器程序,功能是要支持上千万人同时在线,在上线前,做内测,一切OK,可上线后,服务器就支撑不住了,公司的CTO对代码进行优化,再次上线,坚如磐石。那

一瞬间,你就能感受到程序是有灵魂的,就是算法。

3.编程中算法很多,比如八大排序算法(冒泡、选择、插入、快排、归井,希尔、基数、堆排序)、查找算法、分治算法、动态规划算法、KMP算法、贪心算法、普里姆算法、克鲁斯卡尔算法、迪杰斯特拉算法、弗洛伊德算法

4.以骑士周游问题为例,让小伙伴体验用算法去优化程序的意义,让大家直观的感受到算法的威力

2. 经典算法面试题-骑士周游问题  904

2.1 马踏棋盘算法介绍和游戏演示

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

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

3.游戏演示:

https://u.ali213.net/games/horsesun/index.html?game code=403

4. 会使用到图的遍历算法(DFS) +贪心算法优化

骑士周游问题_马踏棋盘

2.2 马踏棋盘算法介绍和游戏演示

1. 马踏棋盘问题(骑士周游问题)实际上是图的深度优先搜索(DFS)的应用。

2. 如果使用回溯(就是深度优先搜索)来解决,假如马儿踏了53个点,如图:走到了第53个,坐标(1,0) ,发现已经走到尽头,没办法,那就只能回退了,查看其他的路径,就在棋盘上不停的回.... ,思路分析+代码实现

3. 先用基本方式来解决,然后使用贪心算法(greedyalgorithm) 进行优化。解决马踏棋盘问题,体会到不同的算法对程序效率的影响

4. 使用前面的游戏来验证算法是否正确

骑士周游问题_骑士周游问题_02


骑士周游问题_递归_03

3. 代码实现  普通方法  906

3.1 思路分析   905

骑士周游问题的解决步骤和思路分析

1.创建棋盘chessBoard,是二维数组

2.将当前位置设置为已经访问,然后根据当前位置,计算马儿还能走哪些位置,并放入到一个集合中(ArrayList),最多有8个,每走一步,使用step + 1

3.遍历ArrayList中存放的所有位置,看看那个可以走,如果可以走通,就继续,走不通,就回溯

4.判断马儿是否完成了任务,使用step 和应该走的步数比较,如果没有达到数量,则表示没有完成任务,将整个棋盘设置为0

骑士周游问题_递归_04

代码在com.stulzl.horse_chessboard

HorseChessBoard

package com.stulzl.horse_chessboard;

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

//马踏棋盘 906
public class HorseChessBoard {
    //定义属性
    private static int X = 6;//表示列col就是横坐标
    private static int Y = 6;//表示行row就是纵坐标
    private static int[][] chessBoard = new int[Y][X];//二位定义数组棋盘
    private static boolean[] visited = new boolean[X*Y];//记录某个位置是否走过
    private static boolean finished = false;//记录马儿是否遍历完棋盘
    public static void main(String[] args) {


        //测试
        //起始坐标,只要起始坐标不变结果就不变
        int row = 5;
        int col = 5;
        long start = System.currentTimeMillis();
        traversalChessBoard(chessBoard,row-1,col-1,1);
        long end = System.currentTimeMillis();
        System.out.println("遍历耗时="+(end-start));

        //输出当前这个棋盘的情况
        //增强for,解释,把二维数组当成几个一维数组,即每行是一个一维数组,然后遍历一维数组
        for(int[] rows : chessBoard){
            for (int step : rows){
                System.out.print(step+"\t");
            }
            System.out.println();
        }

        //普通for
//        for (int i = 0; i < chessBoard.length; i++) {
//            for (int j=0;j
//                System.out.print(chessBoard[i][j]+"\t");
//            }
//            System.out.println();
//        }
    }

    //编写最核心的算法,遍历棋盘,如果成功遍历,就把finished设置位true,并且   907
    //将马儿走的每一步step,记录到chessBoard二维数组中
    public static void traversalChessBoard(int[][] chessBoard,int row,int col,int step){
        //先把step记录到chessBoard中
        chessBoard[row][col] = step;
        //把这个位置设置为已经访问,就是走过了,可以走通,但是走过了,就不用再走了
        visited[row*X+col] = true;
        //获取当前这个位置可以走的下一个位置有哪些
        ArrayList ps = next(new Point(col, row));//注意col是纵坐标即Y,row是横坐标即X
        //遍历
        while(!ps.isEmpty()){//如果集合不为空,就进入循环,然后取出集合里的点
            //解释:取出这个ps集合中的第一个位置(点)(因为remove(0) 0就代表当前集合的第一个元素的索引)
            // ,这里我们取出的点是用的remove删除(解释为什么边取边删除,因为边取边删我们的ps集合将
            // 会越来越小,意思是删掉原来的第一个元素 即索引0对应的元素,后面的第二个元素就变成了第一个元素,
            // 就好像集合往前走了一样的感觉,这样边取边删就可以取出集合中的所有元素,还因为我们不知道集合中
            // 到底有多少个元素,(因为下一步是否合法不确定)就不能用具体的0,1,2……等为索引取出相关数据,
            // 而边取边删就可以,因为删完就结束了),
            // 因为进入循环是用的判断集合是否空作为条件,当我们将当前点的下一步位置的集合边取边删完后
            // ,这个点的集合就不用在进入循环判断了
            Point p = ps.remove(0);//从集合中取出一个点,边取边删的返回值就是这个被删除的点的数据

            //判断该位置是否走过,如果没有走过,我们就让该点递归遍历
            //解释p.y对应row横坐标,p.x对应col纵坐标
            if(!visited[p.y*X+p.x]){//如果不等于true,即false,false就是没有走过
                //该点没走过,就将该点作为当前点(因为我们这个点是从集合中取出来的,就是“下一位合法点”,
                // 要进入递归,就将该点作为当前点) 进入递归
                traversalChessBoard(chessBoard,p.y,p.x,step+1);
            }
        }
        //当推出while循环后,看看是否遍历成功,如果没有成功,就重置相应的值,然后进行回溯
        //解释!finished及为true,因为我们的finished初始值为false,而finished表示游戏结束,我们将
        //!finished及为true就表示游戏还没结束
        if(step<X*Y && !finished){//表示游戏没结束可进入循环
            //重置
            //既然走不通将回溯到该点(注意该点并不一定值起始点,也有可能是递归中的某个分支点)
            chessBoard[row][col] = 0;
            visited[row*X+col] = false;//置为false就当该点没走过
        }else{
            finished = true;//成功结束游戏
        }

    }

    //编写一个方法,可以获取当前位置,可以返回下一步所有合法点位置(Point表示x,y)   906
    public static ArrayList next(Point curPoint){//curPoint当前位置

        //创建一个ArrayList,用来盛放当前坐标curPoint,周围下一步符合合法规则的点(最多8个点)
        ArrayList ps = new ArrayList<>();

        //创建一个Point对象(点/位置),如果该点坐标合法符合规则,放入到ps集合
        Point p1 = new Point();//这个p1就是curPoint下一步的合法点
        //判断在curPoint是否可以走如下位置,如果可以走即合法,就将该点(Point)放入到ps
        //判断是否可以走5位置
        if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y - 1) >= 0) {
            ps.add(new Point(p1)); //这里一定要new Point
        }
        //判断是否可以走6位置
        if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y - 2) >= 0) {
            ps.add(new Point(p1)); //这里一定要new Point
        }
        //判断是否可以走7位置
        if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y - 2) >= 0) {
            ps.add(new Point(p1)); //这里一定要new Point
        }
        //判断是否可以走0位置
        if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y - 1) >= 0) {
            ps.add(new Point(p1)); //这里一定要new Point
        }
        //判断是否可以走1位置
        if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y + 1) < Y) {
            ps.add(new Point(p1)); //这里一定要new Point
        }
        //判断是否可以走2位置
        if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y + 2) < Y) {
            ps.add(new Point(p1)); //这里一定要new Point
        }
        //判断是否可以走3位置
        if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y + 2) < Y) {
            ps.add(new Point(p1)); //这里一定要new Point
        }
        //判断是否可以走4位置
        if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y + 1) < Y) {
            ps.add(new Point(p1)); //这里一定要new Point
        }
        return ps;

    }

}

4. 代码实现  贪心算法  908

4.1 思路分析 908

1.我们现在走的下一个位置,是按照我们的顺时针来挑选位置,因此选择的这个点的下一个可以走的位置的个数是不确定的.

2.优化思路是:我们应该选择下一个的下一个可以走的位置较少的点,开始走,这样可以减少回溯的此时

3.代码:对我们的ps集合按照可以走的下一个位置的次数进行排序,升序排序

骑士周游问题_递归_05

代码在com.stulzl.horse_chessboard_pro  908

HorseChessBoardPro

package com.stulzl.horse_chessboard_pro;

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

//马踏棋盘 贪心算法版 908
public class HorseChessBoardPro {
    //定义属性
    private static int X = 6;//表示列col就是横坐标
    private static int Y = 6;//表示行row就是纵坐标
    private static int[][] chessBoard = new int[Y][X];//二位定义数组棋盘
    private static boolean[] visited = new boolean[X*Y];//记录某个位置是否走过
    private static boolean finished = false;//记录马儿是否遍历完棋盘
    public static void main(String[] args) {


        //测试
        //起始坐标,只要起始坐标不变结果就不变
        int row = 2;
        int col = 2;
        long start = System.currentTimeMillis();
        traversalChessBoard(chessBoard,row-1,col-1,1);
        long end = System.currentTimeMillis();
        System.out.println("遍历耗时="+(end-start));

        //输出当前这个棋盘的情况
        //增强for,解释,把二维数组当成几个一维数组,即每行是一个一维数组,然后遍历一维数组
        for(int[] rows : chessBoard){
            for (int step : rows){
                System.out.print(step+"\t");
            }
            System.out.println();
        }

        //普通for
//        for (int i = 0; i < chessBoard.length; i++) {
//            for (int j=0;j
//                System.out.print(chessBoard[i][j]+"\t");
//            }
//            System.out.println();
//        }
    }

    //写一个方法,对ps的各个位置,可以走的下一个位置的次数进行排序, 把可能走的下一个位置从小到大排序  908
    public static void sort(ArrayList ps) {
        ps.sort(new Comparator() {//这块别纠结了
            @Override
            public int compare(Point o1, Point o2) {
                return next(o1).size()-next(o2).size();
            }
        });
    }

    //编写最核心的算法,遍历棋盘,如果成功遍历,就把finished设置位true,并且   907
    //将马儿走的每一步step,记录到chessBoard二维数组中
    public static void traversalChessBoard(int[][] chessBoard,int row,int col,int step){
        //先把step记录到chessBoard中
        chessBoard[row][col] = step;
        //把这个位置设置为已经访问,就是走过了,可以走通,但是走过了,就不用再走了
        visited[row*X+col] = true;
        //获取当前这个位置可以走的下一个位置有哪些
        ArrayList ps = next(new Point(col, row));//注意col是纵坐标即Y,row是横坐标即X
        sort(ps);//排序后  908
        //遍历
        while(!ps.isEmpty()){//如果集合不为空,就进入循环,然后取出集合里的点
            //解释:取出这个ps集合中的第一个位置(点)(因为remove(0) 0就代表当前集合的第一个元素的索引)
            // ,这里我们取出的点是用的remove删除(解释为什么边取边删除,因为边取边删我们的ps集合将
            // 会越来越小,意思是删掉原来的第一个元素 即索引0对应的元素,后面的第二个元素就变成了第一个元素,
            // 就好像集合往前走了一样的感觉,这样边取边删就可以取出集合中的所有元素,还因为我们不知道集合中
            // 到底有多少个元素,(因为下一步是否合法不确定)就不能用具体的0,1,2……等为索引取出相关数据,
            // 而边取边删就可以,因为删完就结束了),
            // 因为进入循环是用的判断集合是否空作为条件,当我们将当前点的下一步位置的集合边取边删完后
            // ,这个点的集合就不用在进入循环判断了
            Point p = ps.remove(0);//从集合中取出一个点,边取边删的返回值就是这个被删除的点的数据

            //判断该位置是否走过,如果没有走过,我们就让该点递归遍历
            //解释p.y对应row横坐标,p.x对应col纵坐标
            if(!visited[p.y*X+p.x]){//如果不等于true,即false,false就是没有走过
                //该点没走过,就将该点作为当前点(因为我们这个点是从集合中取出来的,就是“下一位合法点”,
                // 要进入递归,就将该点作为当前点) 进入递归
                traversalChessBoard(chessBoard,p.y,p.x,step+1);
            }
        }
        //当推出while循环后,看看是否遍历成功,如果没有成功,就重置相应的值,然后进行回溯
        //解释!finished及为true,因为我们的finished初始值为false,而finished表示游戏结束,我们将
        //!finished及为true就表示游戏还没结束
        if(step<X*Y && !finished){//表示游戏没结束可进入循环
            //重置
            //既然走不通将回溯到该点(注意该点并不一定值起始点,也有可能是递归中的某个分支点)
            chessBoard[row][col] = 0;
            visited[row*X+col] = false;//置为false就当该点没走过
        }else{
            finished = true;//成功结束游戏
        }

    }

    //编写一个方法,可以获取当前位置,可以返回下一步所有合法点位置(Point表示x,y)   906
    public static ArrayList next(Point curPoint){//curPoint当前位置

        //创建一个ArrayList,用来盛放当前坐标curPoint,周围下一步符合合法规则的点(最多8个点)
        ArrayList ps = new ArrayList<>();

        //创建一个Point对象(点/位置),如果该点坐标合法符合规则,放入到ps集合
        Point p1 = new Point();//这个p1就是curPoint下一步的合法点
        //判断在curPoint是否可以走如下位置,如果可以走即合法,就将该点(Point)放入到ps
        //判断是否可以走5位置
        if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y - 1) >= 0) {
            ps.add(new Point(p1)); //这里一定要new Point
        }
        //判断是否可以走6位置
        if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y - 2) >= 0) {
            ps.add(new Point(p1)); //这里一定要new Point
        }
        //判断是否可以走7位置
        if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y - 2) >= 0) {
            ps.add(new Point(p1)); //这里一定要new Point
        }
        //判断是否可以走0位置
        if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y - 1) >= 0) {
            ps.add(new Point(p1)); //这里一定要new Point
        }
        //判断是否可以走1位置
        if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y + 1) < Y) {
            ps.add(new Point(p1)); //这里一定要new Point
        }
        //判断是否可以走2位置
        if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y + 2) < Y) {
            ps.add(new Point(p1)); //这里一定要new Point
        }
        //判断是否可以走3位置
        if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y + 2) < Y) {
            ps.add(new Point(p1)); //这里一定要new Point
        }
        //判断是否可以走4位置
        if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y + 1) < Y) {
            ps.add(new Point(p1)); //这里一定要new Point
        }
        return ps;

    }

}

标签:ps,周游,p1,Point,int,问题,curPoint,new,骑士
From: https://blog.51cto.com/u_15784725/6370452

相关文章

  • 前端解决浏览器跨域问题
      自从前后端分离后,浏览器做出了很多的限制,如产生跨域时将限制访问服务器,那要如何解决前端跨域的问题,下面将以谷歌浏览器(chrome)为标椎来提出一个简单且常用解决方案。 一、创建一个能够跨域的谷歌浏览器下载并安装谷歌浏览器以后(如果已经拥有那就不用),右击谷歌浏览器使用快......
  • pip安装的时候,遇到权限问题
    安装mysql-connector-python,ERROR:CouldnotinstallpackagesduetoanOSError:[Errno13]Permissiondenied:'E:\\tool\\Anaconda\\Lib\\site-packages\\protobuf-3.20.3-py3.9-nspkg.pth'Checkthepermissions. 用管理员打开 ......
  • 银联notifyurl报错302重定向的问题排查,太奇怪了!AspxAutoDetectCookieSupport=1
    用银联的notifyurl接收通知,某一天突然通知没有了,日志里直接没有,就和银联的人一起查,发现错误302重定向。这个notifyurl用浏览器可以正常打开。但是发现打开后会自动追加一段:AspxAutoDetectCookieSupport=1。于是搜,最终发现问题如下图:在会话状态里cookie模式改成了自动检测,导致iis......
  • Spring bean的循环引用问题
    循环依赖:两个或两个以上的bean循环引用。例如:A依赖B,B依赖A。Spring有三种循环依赖问题:(1)构造器的循环依赖:Spring无法解决构造器的循环依赖问题,但是可以使用@Lazy将bean声明为懒加载,什么时候用到这个bean在创建。(2)非单例bean的setter循环依赖:Spring无法解决非单例bean的循环依赖......
  • hadoop序列化相关问题
    什么时候需要使用序列化?需要在不同服务器传递内存数据时,用序列化。序列化后的所有属性需要再反序列化,那么有先后顺序反序列化吗?有的,比如序列化的属性有abc则反序列化的属性必须是cabc数据切片一般为数据块的倍数,为什么?一般一个数据切片对应启动一个maptask任务,可以保证......
  • 前端如何解决跨域问题
      跨域问题是指由于浏览器的同源策略(Same-OriginPolicy:两个页面具有相同的协议、主机和端口,三者有任一不相同即会产生跨域),导致不能在不同域名、协议或端口之间直接进行数据交互。跨域是浏览器的一种安全机制,服务端之间是不存在跨域的。如下表所示:解决方案JSONP:JSONP利用scri......
  • Spring cloud 微服务架构之Ribbon/Fegin连接超时ReadTimeout问题
    问题描述:近期用Springcloud开发微服务架构时候,在服务与服务之间调用调试代码时候,出现链接超时。错误信息:ReadtimedoutexecutingGEThttp://service-batch/batchmanagement/datatransfer/querybyplanid?planid=PL00000102。发生原因:用IDE开发Debug模式调试代码时候,在处理该服......
  • vue3学习中使用vue-router@4的问题Invalid VNode type: undefined (undefined)
    首先是按照常规的箭头函数引入的方法,结果报一下错误,且页面报错constHelloWorld=()=>import('../components/HelloWorld.vue'); 解决办法import{defineAsyncComponent}from'vue'constHelloWorld=defineAsyncComponent(()=>import('../components/HelloWorld.vue......
  • 启动路径问题
    在部署Web应用程序时,可以通过更改路径来更改应用程序的URL,例如从http://localhost:8080/brand-demo更改为http://localhost:8080/myapp。要更改应用程序的路径,可以尝试以下几种方法:修改WAR文件名称:将WAR文件重命名为myapp.war,该文件名将成为应用程序的上下文路径,即应......
  • JasperReport报表导出PDF中文不显示的问题
       首先在JasperReportStudio中加载下载好的中文字体:打开设置页面:Window>>Preferences>>JaspersoftStudio>>Fonts,点击Add添加字体,FamilyName中命名新添加字体名称,TrueType中选择下载的字体文件(.ttf文件),PDFEncoding中选择PDF中中文字体编码格式。     这......