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

概述

骑士周游算法,叫做“马踏棋盘算法”或许更加直观。在国际象棋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));//遍历pswhile (!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){//创建一个ArrayListArrayList<Point> ps = new ArrayList<>();//创建一个PointPoint p1 = new Point();//判断马儿下一步是否可以走,若可以,将这个位置放入集合//判断马儿是否可以走  位置5if ((p1.x=curPoint.x-2)>=0 && (p1.y = curPoint.y-1)>=0){ps.add(new Point(p1));}//判断马儿是否可以走  位置6if ((p1.x=curPoint.x-1)>=0 && (p1.y = curPoint.y-2)>=0){ps.add(new Point(p1));}//判断马儿是否可以走  位置7if ((p1.x=curPoint.x+1) < X && (p1.y = curPoint.y-2)>=0){ps.add(new Point(p1));}//判断马儿是否可以走  位置0if ((p1.x=curPoint.x+2) < X && (p1.y = curPoint.y-1)>=0){ps.add(new Point(p1));}//判断马儿是否可以走  位置1if ((p1.x=curPoint.x+2) < X && (p1.y = curPoint.y+1)< Y){ps.add(new Point(p1));}//判断马儿是否可以走  位置2if ((p1.x=curPoint.x+1)<X && (p1.y = curPoint.y+2)<Y){ps.add(new Point(p1));}//判断马儿是否可以走  位置3if ((p1.x=curPoint.x-1)>=0 && (p1.y = curPoint.y+2)<Y){ps.add(new Point(p1));}//判断马儿是否可以走  位置4if ((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>(){@Overridepublic 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);//遍历pswhile (!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.hqwc.cn/news/126668.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C语言】编译和链接

前言&#xff1a; 编译和链接是计算机程序开发中的两个重要步骤&#xff0c;用于将源代码转化为可执行的程序。 文章目录 一、翻译环境和运行环境二、翻译环境中的编译2.1 预处理&#xff08;预编译&#xff09;2.2 编译2.2.1 语法分析2.2.2 语法分析2.2.3 语义分析 2.3 汇编 三…

【RabbitMQ】初识消息队列 MQ,基于 Docker 部署 RabbitMQ,探索 RabbitMQ 基本使用,了解常见的消息类型

文章目录 前言一、初识消息队列 MQ1.1 同步通信1.2 异步通信1.3 MQ 常见框架及其对比 二、初识 RabbitMQ2.1 什么是 RabbitMQ2.2 RabbitMQ 的结构 三、基于 Docker 部署 RabbitMQ四、常见的消息类型五、示例&#xff1a;在 Java 代码中通过 RabbitMQ 发送消息5.1 消息发布者5.2…

吐血整理,最全Pytest自动化测试框架快速上手(超详细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 pytest框架 pyte…

【深蓝学院】手写VIO第2章--IMU传感器--作业

这次作业坑很多&#xff0c;作业说明的不清楚&#xff0c;摸索了很长时间才将此次作业完成&#xff0c;在这里进行记录。 1. T1 1.1 题干 1.2 解答 1.2.1 法1&#xff0c;ros related方法 不知道为什么我的launch不了&#xff0c;在imu_utils目录下面建立build后&#xff0…

头部品牌停业整顿,鲜花电商的中场战事迎来拐点?

鲜花电商行业再次迎来标志性事件&#xff0c;曾经4年接连斩获6轮融资的明星品牌花加&#xff0c;正式宣布停业整顿。 梳理来看&#xff0c;2015年是鲜花电商赛道的发展爆发期&#xff0c;彼时花加等品牌相继成立&#xff0c;并掀起一波投资热潮&#xff0c;据媒体统计&#xf…

一座“城池”:泡泡玛特主题乐园背后,IP梦想照亮现实

“更适合中国宝宝体质”的主题乐园&#xff0c;被泡泡玛特造出来了。 9月26日&#xff0c;位于北京朝阳公园内的国内首个潮玩行业沉浸式 IP 主题乐园&#xff0c;也是泡泡玛特首个线下乐园——泡泡玛特城市乐园 POP LAND正式开园。 约4万平方米的空间中&#xff0c;泡泡玛特使…

C#学习系列相关之多线程(一)----常用多线程方法总结

一、多线程的用途 在介绍多线程的方法之前首先应当知道什么是多线程&#xff0c; 在一个进程内部可以执行多个任务&#xff0c;而这每一个任务我们就可以看成是一个线程。是程序使用CPU的基本单位。进程是拥有资源的基本单位&#xff0c; 线程是CPU调度的基本单位。多线程的作用…

VF11MR8M 冲销原因 小结

VF11&MR8M 冲销原因 小结 1.后台设置路径&#xff1a; SPRO->财务会计->总账会计->业务交易->调整过账/冲销->定义冲销原因 反记账&#xff1a; 2.前台操作使用01–当前期间回转 不会反记账&#xff0c;冲销凭证 过账日期 按 原凭证过账日期&#xff0…

学习记忆——数学篇——算术——无理数

谐音记忆法 2 \sqrt{2} 2 ​≈1.41421&#xff1a;意思意思而已&#xff1b;意思意思&#xff1b; 3 \sqrt{3} 3 ​≈1.7320&#xff1a;—起生鹅蛋&#xff1b;一起生儿&#xff1b; 5 \sqrt{5} 5 ​≈2.2360679&#xff1a;两鹅生六蛋(送)六妻舅&#xff1b;儿儿生&#xf…

数据结构-----平衡二叉树

目录 前言 1.平衡二叉树 1.1概念与特点 1.2与二叉排序树比较 1.3判断平衡二叉树 2.平衡二叉树的构建 2.1平衡因子 BF 2.2 LL型失衡&#xff08;右旋&#xff09; 2.3 RR型失衡&#xff08;左旋&#xff09; 2.4 LR型失衡&#xff08;先左旋再右旋&#xff09; 2.5 RL…

Next.js 入门笔记

前言 之前初步体验了 React 的魅力, 又看文档理解了一下 useState 和 useEffect, 目前初步理解的概念是: useState 用来声明在组件中使用并且需要修改的变量 useEffect 用来对 useState 声明的变量进行初始化赋值 可能理解的不太准确, 不过大概差不多是这么个意思. 但是再往后…

算法题:买卖股票的最佳时机 II

这道题是贪心算法的中级难度练习题&#xff0c;由于题目设定&#xff0c;整个价格都是透明的&#xff0c;这里并不涉及需要预测股票涨势的问题。解决思路不难&#xff0c;就是一旦股票价格开始下降了就买入&#xff0c;一旦上升了&#xff0c;就赶紧卖出。&#xff08;完整题目…