36.骑士周游算法及其基于贪心算法的优化

news2024/11/25 11:03:26

概述

骑士周游算法,叫做“马踏棋盘算法”或许更加直观。在国际象棋8x8的棋盘中,马也是走“日字”进行移动,相应的产生了一个问题:“如果要求马 在每个方格只能进入一次,走遍全部的64个方格需要如何行进?”这就是著名的 骑士周游算法的由来。
在这里插入图片描述

思路

相信大家看到这个问题首先想到就是回溯
马踏棋盘问题(骑士周游问题) 实际上是图的深度优先搜索(DFS)的应用。
如果使用回溯(就是深度优先搜索) 来解决,假如马儿踏了53个点,走到了第53个,坐标(1,0),发现已经走到尽头,没办法,那就只能回退了,查看其他的路径,就
在棋盘上不停的回溯

基于回溯的解决方案

  1. 创建棋盘chessBoard,是一个二维数组;
  2. 将当前位置设置为已经访问,然后根据当前位置,计算马还能走哪些位置,并放入到一个集合中(ArrayList),最多有8个位置,每走一步,就使用step+1;
  3. 遍历arrayList中存放的所有位置,看看哪个可以走通;
  4. 判断马儿是否完成了任务,使用step和应该走的步数(即棋盘格子数-1)比较,如果没有达到数量,则表示没有完成任务,将整个棋盘置0;
    注:马 不同的走法(策略),会得到不同的结果,效率也会有影响(优化)。

代码实现

public class HorseChessBoard {
    private static int X;//棋盘的列数
    private static int Y;//棋盘的行数
    //创建一个数组, 标记棋盘的各个位置是否被访问过
    private static boolean visited[];
    //试用一个属性,标记是否棋盘的所有位置都被访问过了
    private static boolean finished;//如果为true,表示成功

    public static void main(String[] args) {
        System.out.println("开始执行骑士周游算法~");
        //测试
        X = 8;
        Y = 8;
        int row = 1;//马儿初始位置的行,从1开始编号
        int column = 1;//马儿初始位置的列,从1开始编号
        //创建棋盘
        int[][] chessboard = new int[X][Y];
        visited = new boolean[X*Y];//初始值都是false
        //测试一下耗时
        long start = System.currentTimeMillis();
        traversalCheessBoard(chessboard,row-1,column-1,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();
        }
        System.out.println("骑士周游算法结束");
    }

    /**
     * 骑士周游问题算法
     * @param chessBoard 棋盘
     * @param row 马儿当前位置的行 从0开始
     * @param column 马儿当前位置的列 从0开始
     * @param step 是第几步,初始位置是第1步
     */
    public static void traversalCheessBoard(int[][] chessBoard,int row,int column,int step){
        chessBoard[row][column] = step;
        //row = 4; X=8; column=4; 4*8+4=36;
        visited[row*X+column] = true;//标记该位置已经访问
        //获取当前位置可以走的下一个位置的集合
        ArrayList<Point> ps = next(new Point(column, row));
        //遍历ps
        while (!ps.isEmpty()){
            Point p = ps.remove(0);//取出下一个可以走的位置
            //判断该点是否已经访问过
            if(!visited[p.y*X+p.x]){//说明还没访问过
                traversalCheessBoard(chessBoard,p.y,p.x,step+1);
            }
        }
        //判断马儿是否完成了任务,使用step和应该走的步数(即棋盘格子数-1)比较,
        //如果没有达到数量,则表示没有完成任务,将整个棋盘置0;
        //说明: step<X*Y成立的情况有两种
        //1.棋盘到目前位置,仍然没有走完
        //2.棋盘处于回溯过程
        if (step<X*Y&&!finished){
            chessBoard[row][column]=0;
            visited[row * X + column] = false;
        }else {
            finished = true;
        }
    }

    /**
     * 根据当前位置(Point) ,计算马儿还能走哪些位置(Point),并放入到一个集合中(ArrayList),最多有八个位置
     * @param curPoint
     * @return
     */
    public static ArrayList<Point> next(Point curPoint){
        //创建一个ArrayList
        ArrayList<Point> ps = new ArrayList<>();
        //创建一个Point
        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;
    }
}

效率分析

采用回溯的方案思路上自然是可行的,那么它的效率究竟如何呢?可以说很不乐观!测算下来差不多要40秒左右,优化的空间很大。
在这里插入图片描述

回溯分析与贪心优化

我们思考可以在此思考一下上面解决方案的是否有可以优化的地方?能否用贪心算法进行优化呢?

  1. 我们获取当前位置,可以走的下一个位置的集合:
    ArrayList ps = next(new Point(column,row));
  2. 需要对ps中所有Point 下一步的所有集合数目进行非递减排序;
    a. 递减是:9,7,6,5,4…
    b. 递增排序:4,5,6,7,8…
    c. 非递减排序: 1,2,2,3,3,4,4,4,4,4,4,4,5,8,10…
    d. 非递增排序: 9,9,9,8,7,5,3…
  3. 如果下一步的选择越少,意味着回溯时的步骤越少,相应的效率也会越高,所以我们应该采用非递减排序,使得回溯的代价尽可能的低。

核心优化代码

我们不妨编写一个方法,根据当前这一步的所有下一步的选择位置,进行非递减排序,以求减少回溯的次数

public static void sort(ArrayList<Point> ps){
        ps.sort(new Comparator<Point>(){
            @Override
            public int compare(Point o1, Point o2) {
                //获取到o1的下一步的所有位置个数
                int count1 = next(o1).size();
                //获取到o2的下一步的所有位置个数
                int count2 = next(o2).size();
                if (count1<count2){
                    return -1;
                }else if (count1==count2){
                    return 0;
                }else {
                    return 1;
                }
            }
        });
    }

这样,在上面的回溯算法中,我们可以先对ps进行排序处理,再进行后面的测算

		//获取当前位置可以走的下一个位置的集合
        ArrayList<Point> ps = next(new Point(column, row));

		//对ps进行排序,排序的规则就是对ps的所有的Point对象的下一步的位置数目进行非递减排序
        sort(ps);

        //遍历ps
        while (!ps.isEmpty()){
            Point p = ps.remove(0);//取出下一个可以走的位置
            //判断该点是否已经访问过
            if(!visited[p.y*X+p.x]){//说明还没访问过
                traversalCheessBoard(chessBoard,p.y,p.x,step+1);
            }
        }

效率分析

经过贪心算法的优化后,相同的配置下,测算时间直接降到了50ms,效率比之前提升600倍。还是很可观的提升的。
在这里插入图片描述

小结

本节,先是采用回溯算法对骑士周游问题进行了拆解,而后利用贪心算法对回溯算法进行了优化解决了骑士周游问题。相信借此我们对贪心算法的应用应该都有了更深层次的理解,算法千万条,应用第一条,只有在合适的场景才能发挥出其最大的作用。


关注我,共同进步,每周至少一更。——Wayne

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1053752.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【STM32基础 CubeMX】按键的检测

文章目录 前言一、按键原理图分析二、cubeMX配置key GPIO三、代码分析3.1 cubemx生成的代码3.2 1个库函数 四、按键点灯示例代码总结 前言 在嵌入式系统开发中&#xff0c;按键检测是一个基础而重要的任务&#xff0c;特别是在使用STM32系列微控制器的项目中。按键通常被用于与…

C++ - 开放地址法的哈希介绍 - 哈希表的仿函数例子

前言 哈希其实是一种搜索方式&#xff0c;像暴力查找&#xff0c;有序数组的二分查找&#xff0c;二分查找就很快了&#xff0c;可以达到O(log n)。但是有序数组有一个 弊端&#xff0c;就是要先进行排序&#xff0c;这就有消耗&#xff0c;这还好&#xff0c;当要插入删除修改…

Go-Python-Java-C-LeetCode高分解法-第八周合集

前言 本题解Go语言部分基于 LeetCode-Go 其他部分基于本人实践学习 个人题解GitHub连接&#xff1a;LeetCode-Go-Python-Java-C 欢迎订阅CSDN专栏&#xff0c;每日一题&#xff0c;和博主一起进步 LeetCode专栏 本文部分内容来自网上搜集与个人实践。如果任何信息存在错误,欢迎…

UE5.1编辑器拓展【一、脚本化资产行为,通知,弹窗,高效复制多个同样的资产】

目录​​​​​​​ 插件制作 添加新的类&#xff1a;AssetActionUtility 添加新的模块&#xff1a;EditorScriptingUtilities 路径了解 添加debug的头文件 代码【debug.h】内涵注释&#xff1a; 写函数 .h文件 .cpp文件 插件制作 首先第一步是做一个插件&#xff1a…

Flink中序列化RoaringBitmap不同方式的对比

背景 在flink中&#xff0c;我们有时候会使用到RoaringBitmap进行统计计数等操作&#xff0c;而当使用RoaringBitmap时&#xff0c;这就涉及到了最重要的问题&#xff0c;如何序列化&#xff1f;序列化的目的是为了进行网络通信或者状态序列化的目的&#xff0c;本文的重点是比…

根据GWAS数据估算样本量N和使用千人基因组填充maf的参考文献

https://github.com/GenomicSEM/GenomicSEM/wiki/2.1-Calculating-Sum-of-Effective-Sample-Size-and-Preparing-GWAS-Summary-Statistics

【LeetCode热题100】--104.二叉树的最大深度

104.二叉树的最大深度 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, TreeNode left, TreeNode right) …

尚硅谷谷粒商城部分报错问题处理

1、启动报错&#xff1a; 内容&#xff1a; org.springframework.beans.factory.BeanCreationException: Error creating bean with name attrAttrgroupRelationController: Lookup method resolution failed; nested exception is java.lang.IllegalStateException: Failed t…

使用prometheus监控java服务

在prometheus官方下载页面没有看到jvm_exproter的下载地址但是官方页面是有推荐下载地址的 访问 Prometheus - Monitoring system & time series database prometheus官方网址 官方推荐地址下载是在github网络访问不方便的可以用下面的网址 wget https://repo1.maven…

【小程序 - 基础】页面导航、页面事件、生命周期、WXS脚本_04

目录 一、页面导航 1. 什么是页面导航 2. 小程序中实现页面导航的两种方式 2.1 声明式导航 2.1.1 导航到 tabBar 页面 2.1.2 导航到非 tabBar 页面 2.1.3 后退导航 2.2 编程式导航 2.2.1 导航到 tabBar 页面 2.2.2 导航到非 tabBar 页面 2.2.3 后退导航 2.3. 导航…

Proxyer实现内网穿透云服务器

Proxyer Proxyer是一个网络代理工具&#xff0c;它可以将本地计算机的网络流量&#xff08;如HTTP、HTTPS、TCP等&#xff09;转发到远程服务器。使用Proxyer可以在本地计算机上建立一个代理服务器&#xff0c;通过代理服务器来访问互联网上的资源。 yum仓库设置 rm -f /etc…

在Ubuntu上通过Portainer部署微服务项目

这篇文章主要记录自己在ubuntu上部署自己的微服务应用的过程&#xff0c;文章中使用了docker、docker-compose和portainer&#xff0c;在部署过程中遇到了不少问题&#xff0c;因为博主也是初学docker-compose&#xff0c;通过这次部署实战确实有所收获&#xff0c;在这篇文章一…

LeetCode【121. 买卖股票的最佳时机】

你才不是什么小人物&#xff0c;你在我这里&#xff0c;是所有的天气和心情。 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某一个不同的日子 卖出该股票。设计一…

idea Springboot 校园助学贷款系统VS开发mysql数据库web结构java编程计算机网页源码maven项目

一、源码特点 springboot 校园助学贷款系统是一套完善的信息系统&#xff0c;结合springboot框架和bootstrap完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用springboot框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统 具有完整的源代码和数据库&…

小谈设计模式(8)—代理模式

小谈设计模式&#xff08;8&#xff09;—代理模式 专栏介绍专栏地址专栏介绍 代理模式代理模式角色分析抽象主题&#xff08;Subject&#xff09;真实主题&#xff08;Real Subject&#xff09;代理&#xff08;Proxy&#xff09; 应用场景远程代理虚拟代理安全代理智能引用代…

ubuntu22.04 x11窗口环境手势控制

ubuntu22.04 x11窗口环境手势控制 ubuntu x11窗口环境的手势控制并不优秀&#xff0c;我们可以使用touchegg去代替 这个配置过程非常简单&#xff0c;并且可以很容易在一定范围内达到你想到的效果&#xff0c;类比mac的手势控制 关于安装 首先添加源&#xff0c;并安装 sud…

数据结构-----二叉排序树

目录 前言 1.什么是二叉排序树 2.如何构建二叉排序树 3.二叉排序树的操作 3.1定义节点储存方式 3.2插入节点操作 3.2创建二叉排序树 3.4遍历输出&#xff08;中序遍历&#xff09; 3.5数据查找操作 3.6获取最大值和最小值 3.7删除节点操作 3.8销毁二叉排序树 4.完…

漏斗分析模型

从业务流程起点开始到最后日标完成的每个环节都会有用户流失&#xff0c;因此需要一种分析方法来衡量业务流程每一步的转化效率&#xff0c;漏斗分析方法就是这样的分析方法。 例如&#xff0c;在淘宝上一款商品的浏览量是 300、点击量是 100、订单量是 20、支付量是 10&#…

centos 部署nginx 并配置https

centos版本&#xff1a;centos 7.8 &#xff08;最好不要用8&#xff0c;8的很多用法和7相差很大&#xff09; 一.安装nginx 1。下载Nginx安装包&#xff1a;首先&#xff0c;访问Nginx的官方网站&#xff08;https://nginx.org/&#xff09;或您选择的镜像站点&#xff0c;找…

Linux知识点+命令

1. 简介 Linux 是一套免费使用和自由传播的类 Unix 操作系统&#xff0c;是一个基于 POSIX 和 UNIX 的多用户、多任务、支持多线程和多 CPU 的操作系统。 Linux 能运行主要的 UNIX 工具软件、应用程序和网络协议。它支持 32 位和 64 位硬件。Linux 继承了 Unix 以网络为核心的…