前言需求

今天我们学习的是马踏棋盘算法,我们还是从一个场景里引入看看

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

将马随机放在国际象棋的6×6棋盘Board0~5的某个方格中

提示:马按走棋规则(马走日字)进行移动

要求:每个方格只进入一次,走遍棋盘上全部64个方格

一、马踏棋盘问题

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

还记得图的深度优先搜索(DFS)吗?

有些模糊或者不记得小伙伴可以看往期文章:图(广度优先与深度优先)

那么按照我们的简单思路,是不是要一个位置一个位置去踩坑看看?

那么按照我们的深度优先搜索,就要一步步走下去,直至达成任务

当我们的所选第三步的位置,无法达成完成任务

那么我们需要回溯,将原第三步更换到下一个位置里去

在以新第三步开始,进行搜索,也要一步步走下去,直至达成任务

二、通过示例来认识算法

根据我们之前简单的思路,首先我们需要创建一个棋盘的数组

当我们做出选择下一步的时候,我们需要将当前的位置标记为已访问,并根据当前位置计算出马儿能走那些位置,并放入到一个集合中里去

当然我们可以根据棋盘的情况来判断是否可以进行计算

注意::马儿不同的走法、会得到不同的结果,效率也会有影响(需优化)

规则判断是否可走

那么我怎么知道这些位置是否可走呢?我是怎么计算出来的呢?

首先我们先分析当前位置的x、y坐标,按照规则进行计算:(马走日字)

我们先分析一下象棋里的马走日是怎么样的吧

马走日所说的是马从提棋位置到落棋位置是一个“日”子的对角线,在没有棋子踩住马脚时,马是可以随意走哪个方向的日字都是可以的

在有其他棋子在马的如图相关位置时,马就不能走该方向的日字了,我们也熟称“踩马脚了”。注意无论踩马脚的棋子是己方的棋子还是敌方的棋子,被踩方向的日字都不能走了

如果四只马脚都被踩了,那么这只马哪里都走不了了(如图)

在我们这个问题中,还请你看图关联看懂马儿怎么走的,即称马走日

当我们知道规则怎么玩了,就可以从图上看出来,每个点与当前点的关系

那么我们的马儿剩下的点与当前是什么关系呢?怎么走呢?

骑士周游算法思路

我们创建一个类存放棋盘行、列,并记录棋盘上的是否被访问过public class HorseChessboard {

private static int x;//棋盘的列数

private static int y;//棋盘的行数

//创建一个数组,标记棋盘的各个位置是否被访问过

private static boolean visited[];

//使用一个属性,标记是否棋盘的所有位置都被访问

private static boolean finished; // 如果为true,表示成功

}

我们使用Point 类来表示 (x, y) 坐标空间中的位置的点public class Point extends Point2D implements java.io.Serializable {

public int x;

public int y;

private static final long serialVersionUID = -5276940640259749850L;

public Point() {

this(0, 0);

}

public Point(Point p) {

this(p.x, p.y);

}

public Point(int x, int y) {

this.x = x;

this.y = y;

}

//以双精度型返回点的 X 坐标。

public double getX() {

return x;

}

//以双精度型返回点的 Y 坐标。

public double getY() {

return y;

}

//返回此点的位置。

@Transient

public Point getLocation() {

return new Point(x, y);

}

//将点的位置设为指定位置

public void setLocation(Point p) {

setLocation(p.x, p.y);

}

//将此点更改为具有指定位置

public void setLocation(int x, int y) {

move(x, y);

}

//将此点的位置设为指定的双精度坐标

public void setLocation(double x, double y) {

this.x = (int) Math.floor(x+0.5);

this.y = (int) Math.floor(y+0.5);

}

//将此点移动到 (x,y) 坐标平面中的指定位置。

public void move(int x, int y) {

this.x = x;

this.y = y;

}

//平移 (x,y) 位置的点,沿 x 轴平移 dx,沿 y 轴平移 dy,移动后得到点 (x+dx, y+dy)

public void translate(int dx, int dy) {

this.x += dx;

this.y += dy;

}

//确定两个点是否相等。

public boolean equals(Object obj) {

if (obj instanceof Point) {

Point pt = (Point)obj;

return (x == pt.x) && (y == pt.y);

}

return super.equals(obj);

}

// 返回此点的字符串表示形式及其在 (x,y) 坐标空间中的位置

public String toString() {

return getClass().getName() + "[x=" + x + ",y=" + y + "]";

}

}

根据思路,需要根据当前位置判断马儿能走那些位置,并将结果放入ArrayList集合中

public class HorseChessboard {

//省略其他关键性代码....

/**

* 功能:根据当前位置(Point对象),计算马儿还能走哪些位置(Point),并放入到一个集合中(ArrayList),最多有8个位置

* @param curPoint

* @return

*/

public static ArrayList next(Point curPoint){

ArrayList 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));

}

return ps;

}

}

而其他点的位置与当前位置关系,我们之前也使用图解的方式分析,现在代码实现

public class HorseChessboard {

//省略其他关键性代码....

/**

* 功能:根据当前位置(Point对象),计算马儿还能走哪些位置(Point),并放入到一个集合中(ArrayList),最多有8个位置

* @param curPoint

* @return

*/

public static ArrayList next(Point curPoint){

ArrayList 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;

}

}

那么会不会有小伙伴有疑惑??

为什么走五那个位置就要>=0呢,走七的位置就要

我们先分析走五的位置的时候,为什么要>=0

同理,小于x,小于y代表我们只选择在棋盘内的点,超出的则不能走

骑士周游算法实践

往期我们使用的是二维数组代表这个点是否被访问过

但这里是36步都需要走一遍,那么我们其实可以使用一维数组进行表示

这样我们可以是用公式:马儿所在行 * 棋盘行 +马儿所在列 = 马儿下标 + 1public class HorseChessboard {

//省略其他关键性代码....

/**

* 完成骑士周游问题的算法

* @param chessboard 棋盘

* @param row 马儿当前的位置的行 从0开始

* @param column 马儿当前的位置的列 从0开始

* @param step 是第几步 ,初始位置就是第1步

*/

public static void traversalChessboard(int[][] chessboard, int row, int column, int step) {

//标记当前棋盘执行的是第几步

chessboard[row][column] = step;

//row = 3 X = 6 column = 3 = 3 * 6 + 3 = 21 -1 = 20

visited[row * x + column] = true; //标记该位置已经访问

//获取当前位置可以走的下一个位置的集合

ArrayList ps = next(new Point(column, row));

}

}

当我们获取到当前位置可以走的下一个位置的集合,就进行遍历递归public class HorseChessboard {

//省略其他关键性代码....

/**

* 完成骑士周游问题的算法

* @param chessboard 棋盘

* @param row 马儿当前的位置的行 从0开始

* @param column 马儿当前的位置的列 从0开始

* @param step 是第几步 ,初始位置就是第1步

*/

public static void traversalChessboard(int[][] chessboard, int row, int column, int step) {

//标记当前棋盘执行的是第几步

chessboard[row][column] = step;

//row = 3 X = 6 column = 3 = 3 * 6 + 3 = 21 -1 = 20

visited[row * x + column] = true; //标记该位置已经访问

//获取当前位置可以走的下一个位置的集合

ArrayList ps = next(new Point(column, row));

//遍历 ps

while(!ps.isEmpty()) {

Point p = ps.remove(0);//取出下一个可以走的位置

//判断该点是否已经访问过

if(!visited[p.y * X + p.x]) {//说明还没有访问过

traversalChessboard(chessboard, p.y, p.x, step + 1);

}

}

}

}

我们怎么区分当前节点的可以走的下一个位置的集合,是否一路就成功了呢?

使用step 和 应该走的步数比较:step = X * Y

假如当前节点的可以走的下一个位置的集合,没有一路就成功,怎么办?

取消该位置已访问,并将棋盘置为0,说明该节点处于回溯状态public class HorseChessboard {

//省略其他关键性代码....

/**

* 完成骑士周游问题的算法

* @param chessboard 棋盘

* @param row 马儿当前的位置的行 从0开始

* @param column 马儿当前的位置的列 从0开始

* @param step 是第几步 ,初始位置就是第1步

*/

public static void traversalChessboard(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 ps = next(new Point(column, row));

//遍历 ps

while(!ps.isEmpty()) {

Point p = ps.remove(0);//取出下一个可以走的位置

//判断该点是否已经访问过

if(!visited[p.y * x + p.x]) {//说明还没有访问过

traversalChessboard(chessboard, p.y, p.x, step + 1);

}

}

//判断马儿是否完成了任务,使用 step 和应该走的步数比较 ,

//如果没有达到数量,则表示没有完成任务,将整个棋盘置0

//说明: step < X * Y 成立的情况有两种

//1. 棋盘到目前位置,仍然没有走完

//2. 棋盘处于一个回溯过程

if(step < x * y && !finished ) {

chessboard[row][column] = 0;

visited[row * x + column] = false;

} else {

finished = true;

}

}

}

接下来,让我们使用demo 测试一把这些思路与代码

我们采用上图的马儿作为起始位置,来测试看看public class HorseChessboard {

//省略其他关键性代码....

public static void main(String[] args) {

System.out.println("骑士周游算法,开始运行~~");

//测试骑士周游算法是否正确

x = 6;

y = 6;

int row = 4; //马儿初始位置的行,从1开始编号

int column = 3; //马儿初始位置的列,从1开始编号

//创建棋盘

int[][] chessboard = new int[x][y];

visited = new boolean[x * y];//初始值都是false

//测试一下耗时

long start = System.currentTimeMillis();

traversalChessboard(chessboard, row - 1, column - 1, 1);

long end = System.currentTimeMillis();

System.out.println("共耗时: " + (end - start) + " 毫秒");

//输出棋盘的最后情况

for(int[] rows : chessboard) {

for(int step: rows) {

System.out.print(step + "\t");

}

System.out.println();

}

}

}

运行结果如下:

骑士周游算法,开始运行~~

共耗时: 40 毫秒

08 03 10 29 32 05

17 28 07 04 11 30

02 09 18 31 06 33

27 16 01 20 23 12

36 19 14 25 34 21

15 26 35 22 13 24

三、使用贪心思想进行优化

利用贪心算法的思想,对下一步的所有集合的数目, 进行非递减排序

什么是非递减?

递增的情况是:1、2、3、4、5、6、7、8、9

递减的情况是:9、8、7、6、5、4、3、2、1

非递增的情况是:9、8、7、6、5、5、4、3、2、1

非递减的情况是:1、2、2、3、3、4、4、5、6、7

目的:使马儿走的下一步是下一步集合中可选性最少的,减少回溯可能性public class HorseChessboard {

//省略其他关键性代码....

//根据当前这个一步的所有的下一步的选择位置,进行非递减排序, 减少回溯的次数

public static void sort(ArrayList ps) {

ps.sort(new Comparator() {

@Override

public int compare(Point o1, Point o2) {

// TODO Auto-generated method stub

//获取到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;

}

}

});

}

}

那么怎么使用呢,我们在算法里进行排序优化public class HorseChessboard {

//省略其他关键性代码....

/**

* 完成骑士周游问题的算法

* @param chessboard 棋盘

* @param row 马儿当前的位置的行 从0开始

* @param column 马儿当前的位置的列 从0开始

* @param step 是第几步 ,初始位置就是第1步

*/

public static void traversalChessboard(int[][] chessboard, int row, int column, int step) {

//标记当前棋盘执行的是第几步

chessboard[row][column] = step;

//row = 3 X = 6 column = 3 = 3 * 6 + 3 = 21 -1 = 20

visited[row * x + column] = true; //标记该位置已经访问

//获取当前位置可以走的下一个位置的集合

ArrayList 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]) {//说明还没有访问过

traversalChessboard(chessboard, p.y, p.x, step + 1);

}

}

}

}public class HorseChessboard {

//省略其他关键性代码....

public static void main(String[] args) {

System.out.println("骑士周游算法,开始运行~~");

//测试骑士周游算法是否正确

x = 6;

y = 6;

int row = 4; //马儿初始位置的行,从1开始编号

int column = 3; //马儿初始位置的列,从1开始编号

//创建棋盘

int[][] chessboard = new int[x][y];

visited = new boolean[x * y];//初始值都是false

//测试一下耗时

long start = System.currentTimeMillis();

traversalChessboard(chessboard, row - 1, column - 1, 1);

long end = System.currentTimeMillis();

System.out.println("共耗时: " + (end - start) + " 毫秒");

//输出棋盘的最后情况

for(int[] rows : chessboard) {

for(int step: rows) {

System.out.print(step + "t");

}

System.out.println();

}

}

}

运行结果如下:

骑士周游算法,开始运行~~

共耗时: 9 毫秒

08 03 10 29 32 05

17 28 07 04 11 30

02 09 18 31 06 33

27 16 01 20 23 12

36 19 14 25 34 21

15 26 35 22 13 24

从40毫秒 到9毫秒 这个速度还是很客观的,相比之前的算法更优一些

java 马踏棋盘优化_我所知道的十大常用算法之马踏棋盘算法(深度搜索、贪心思想优化 )...相关推荐

  1. 我所知道的十大常用算法之克鲁斯尔算法(最小生成树)

    前言需求 今天我们学习的是克鲁斯尔算法,我们还是从一个场景里引入看看 有7个村庄(A, B, C, D, E, F, G) ,现在需要修路把7个村庄连通 1.各个村庄的距离用边线表示(权) ,比如 A ...

  2. 我所知道的十大常用算法之普里姆算法(最小生成树)

    前言需求 今天我们学习的是普里姆算法,我们还是从一个场景里引入看看 有7个村庄(A, B, C, D, E, F, G) ,现在需要修路把7个村庄连通 1.各个村庄的距离用边线表示(权) ,比如 A ...

  3. 优化Android App性能?十大技巧

    优化Android App性能?十大技巧 android shangxuetang 1年前 (2014-05-27) 3399℃ 4评论 android 无论锤子还是茄子手机的不断冒出,Android ...

  4. 大于小于优化_架构 - 以MySQL为例,详解数据库索引原理及深度优化

    一.摘要 本文以MySQL数据库为研究对象,讨论与数据库索引相关的一些话题.特别需要说明的是,MySQL支持诸多存储引擎,而各种存储引擎对索引的支持也各不相同,因此MySQL数据库支持多种索引类型,如 ...

  5. mysql快速建表工具_我所知道的MYSQL快速建表的3种方法

    方法一:创建一模一样的表结构(包括索引,不包括表中数据)mysql> desc t_my_series; +-------------+------------+------+-----+--- ...

  6. 视频会议 sdk 选择_企业业务的十大热门视频通话和视频会议API / SDK提供程序

    视频会议 sdk 选择 Statista.com Statista.com Video conferencing App / Country Hangouts Meet Houseparty Ms T ...

  7. 十大被低估的python库_小白必读!十大被低估的Python自带库!

    原标题:小白必读!十大被低估的Python自带库! 大家在学习python的过程中,都会了解到python的一个强大的功能在于各种强大的第三方库函数,大家只需要通过pip install 即可安装我们 ...

  8. 优化mysql数据库_MySQL数据库十大优化技巧

    WEB开发者不光要解决程序的效率问题,对数据库的快速访问和相应也是一个大问题.希望本文能对大家掌握MySQL优化技巧有所帮助. 1. 优化你的MySQL查询缓存 在MySQL服务器上进行查询,可以启用 ...

  9. mysql入门优化_MySQL数据库:MySQL十大优化技巧详解

    本文主要向大家介绍了MySQL数据库的MySQL十大优化技巧详解,通过具体的内容向大家展现,希望对大家学习MySQL数据库有所帮助. WEB开发者不光要解决程序的效率问题,对数据库的快速访问和相应也是 ...

最新文章

  1. 什么是 SAP Spartacus FacadeFactoryService 中的 Resolver
  2. .NET 下基于动态代理的 AOP 框架实现揭秘
  3. R-CNN 最直观的理解
  4. 吴恩达教授机器学习笔记【一】- 线性回归(2)
  5. linux下的用户的管理(创建用户,删除用户)
  6. 李阳疯狂英语900句 331-545
  7. Cordova框架基本原理
  8. 基于STM32C8T6F103实现串口通信
  9. Android 友盟分享(截图指定的View分享)
  10. html如何加载ae做好的,AE转JS动画,lottie.js和bodymovin的简易使用心得
  11. multisim二极管_每日干货——光敏二极管传感器
  12. 18年研赛数学建模心得
  13. 【开发随机】JAVA+POI+自定义注解+反射构建自定义工具类实现快捷简便的Excel模板化导出(附demo代码)
  14. 数据可视化中的格式塔心理学
  15. DaoCloud道客:云原生多云应用利器–Karmada总览篇
  16. matlab 去高光,图像处理-去高光/直方图均衡
  17. javacv开发详解之19:如何使用批量的多张图片制作apng动态图,再也不用担心不会制作动态图了
  18. 什么是命名路由?命名路由,命名路由意义
  19. 基于STM32的无线通信模块使用——HC_05蓝牙串口
  20. maven java archetype_Maven-自定义工程骨架archetype

热门文章

  1. 微型计算机技术怎么学,浅谈微型计算机技术课程的启发式教学
  2. 【渝粤教育】国家开放大学2018年秋季 0691-21T物理化学及实验 参考试题
  3. 【渝粤教育】国家开放大学2018年秋季 0299-21T中国古代文学(1) 参考试题
  4. [渝粤教育] 中国地质大学 操作系统原理(新) 复习题
  5. 【渝粤题库】国家开放大学2021春1359高级英语写作题目
  6. 物联网无线数传通信模块:工业级高精度电源模块
  7. 大功率无线数传设备不接天线有何影响?
  8. 下载anaconda时出现“Please make sure you are connected to the internet”警告
  9. android bench内存测试,华为p10内存测试软件(androbench) v5.0.1 免费版
  10. 【黑客帝国数字雨屏保】基于Win32的黑客帝国数字雨屏幕保护程序(附VS工程代码文件和可执行文件)