文章目录

  • 前言
  • 一、算法分析
  • 二、算法设计
  • 三、算法实现
  • 四、演示(OneStrokeV1.0)
  • 五、有待改进
    • 1.不符合算法的有穷性,可能无法得到通关的路径
    • 2.空间和时间复杂度高,IDEA的资源开销非常大
  • 总结

前言

微信在几年前有一个比较火的小游戏,叫“一笔画完”(如图为游戏的第15关)。游戏规则是根据游戏界面,由起点开始,一笔画完所有的格子即为通关。本文章就是通过设计一个Java程序,输入游戏界面的状态,而由代码执行出我们通关的路径。

一、算法分析

首先,通过分析游戏界面,不难得出以下五点:

第一点:游戏界面是一个矩阵,如游戏第15关是一个4行4列(4×4)的矩阵

第二点:每一个格子有三种状态,即无效、走过和未走过
注:以上面的第15关游戏界面为例,把其当做4×4的矩阵,那么第一行的第1个、第3个、第4个和第四行的第1个格子是游戏中用不上的格子,即为无效格子。

第三点:格子最多有四个方向,即向上、向下、向左、向右

第四点:所有的格子均被走过为通关

结论:用穷举法,暴力破解,因为穷举法最符合人脑的思维方式,可以先码出一个程序再说

二、算法设计

经过上述的分析,可以一一对应地设计:

第一步:创建一个二维的整型数组,行数和列数由输入的整数决定,起点也由输入决定

第二步:二维数组的数字由0、1组成,1为无效和走过的格子、0为未走过的格子

第三步:格子的上、下、左、右的移动。在移动前先判断此格子有哪几种可选择的路径方向,然后在可选择的方向中随机选一种,如果没有选择的方向,就重新开始,直至达到第四步的要求。格子的移动采用递归

向上移动:行下标-1、列下标不变向下移动:行下标+1、列下标不变向左移动:行下标不变、列下标-1向右移动:行下标不变、列下标+1

第四步:如果此格子没有可选择的方向且二维数组未走过(0)的格子数量为1,即为通关,打印出路径,即下面这种情况:

三、算法实现

好了,上代码,先创建出几个基本方法:

方法一、int[] Channel(int arr[][], int i, int j):根据传入的2维数组和坐标,返回此格子可选择的方向集合m,代码如下:

    /*** 找出此位置有几种选择的方向* @param arr 游戏矩阵* @param i 传入的坐标i* @param j 传入的坐标j* @return 可选择的方向数组,长度为4,分别代表上,下,左,右*/public static int[] Channel(int arr[][], int i, int j){int[] num = new int[4];if(i > 0 && arr[i - 1][j] != 1) {                   //上num[0] = 1;}if(i < arr.length - 1 && arr[i + 1][j] != 1){       //下num[1] = 1;}if(j > 0 && arr[i][j - 1] != 1) {                   //左num[2] = 1;}if(j < arr[0].length - 1 && arr[i][j + 1] != 1) {   //右num[3] = 1;}return num;}

注:m为一维整形数组,长度为4,对应格子的上、下、左、右四个方向,用0、1填充,1为次格子的该方向可以走,0反之

方法二、boolean isNotChoice(int arr[][]):判断传入的2维矩阵是否存在没有方向选择的格子,有返回True,代码如下:

    /*** 判断矩阵中是否存在可选择路径为0的坐标* @param arr 游戏矩阵* @return 有返回false,没有返回true*/public static boolean isNotChoice(int arr[][]){for(int i = 0; i < arr.length; i++){for(int j = 0; j < arr[0].length; j++){if (arr[i][j] == 0 && NotZero(Channel(arr, i, j)) == 0)return true;}}return false;}

注:NotZero()方法作用为找出格子放回的m数组中1(可走)的个数,代码如下:

    /*** 找出方向选择数组中可选择方向的数量* @param arr 方向选择数组* @return 返回方向选择数组中可选择方向的数量*/public static int NotZero(int arr[]){int n = 0;for(int i = 0; i < 4; i++){if(arr[i] != 0){n++;}}return n;}

方法三、int Zero(int arr[][]):返回2维数组中0(未走过)的个数,代码如下:

    /*** 判断矩阵中0的数量,即未走过的坐标* @param arr 游戏矩阵* @return 返回矩阵中未走过的坐标数量(int)*/public static int Zero(int arr[][]){int end = 0;for(int i = 0; i < arr.length; i++){for(int j = 0; j < arr[0].length; j++){if (arr[i][j] == 0)end++;}}return end;}

方法四:int FindPos(int n, int arr[]):返回产生的随机数在数组中不为零的位置
例:一个格子的方向集合m,产生的随机数n如下

m = {0,1,0,1}(下、右)          n = 0[0,2)     FindPos(n,m) = 1(下)
m = {0,1,1,1}(下、左、右)           n = 2[0,3)     FindPos(n,m) = 3(右)
m = {1,0,0,1}(上、右)         n = 1[0,2)     FindPos(n,m) = 3(右)

代码如下:

    /*** 找出方向选择数组中第n个不为0的数* @param n 整型* @param arr 方向选择数组* @return 返回第n个不为0的元素的下标*/public static int FindPos(int n, int arr[]){int m = 0;for(int i = 0; i < arr.length; i++){if(arr[i] == 1)m++;if(m == n + 1) {return i;}}return 0;}

方法五:主方法Start(),代码如下:

/*** 暴力破解开始* @param arr 游戏矩阵* @param i 起点坐标的i值,从0开始* @param j 起点坐标的j值,从0开始* @param map 存储执行路线的字符矩阵* @param blank 无效坐标ID数组* @param start 起点坐标* @param matrix 存储矩阵参数数组* @param count 统计暴力破解次数*/public static void Start(int arr[][], int i, int j, char[][] map, int[] blank, int[] start, int[] matrix, int count){System.out.println("-----------------------");PrintArray(map);//如果矩阵中还存在未走过(0)的格子,就进入循环while(Zero(arr) != 0) {//根据传入的二维矩阵和坐标,计算该格子可选择的方向,返回方向选择集合mint[] m = Channel(arr, i, j);//如果m中可选择(1)的个数大于0,则进行随机选择一个方向进行移动if (NotZero(m) > 0 && !isNotChoice(arr)) {//将此位置置为1,即"走过"arr[i][j] = 1;//根据可选择方向数量,随机选择一个方向,进行移动int random = (int) (Math.random() * NotZero(m));//找出此随机数在方向选择数组m中代表的方向int n = FindPos(random, m);//根据n进行移动if (n == 0) {                                               //上if (i > 0 && arr[i - 1][j] != 1) {map[i][j] = '↑';Start(arr, i - 1, j, map, blank, start, matrix, count);}}if (n == 1) {if (i < arr.length - 1 && arr[i + 1][j] != 1) {         //下map[i][j] = '↓';Start(arr, i + 1, j, map, blank, start, matrix, count);}}if (n == 2) {if (j > 0 && arr[i][j - 1] != 1) {                      //左map[i][j] = '←';Start(arr, i, j - 1, map, blank, start, matrix, count);}}if (n == 3) {if (j < arr[0].length - 1 && arr[i][j + 1] != 1) {      //右map[i][j] = '→';Start(arr, i, j + 1, map, blank, start, matrix, count);}}//如果m中可选择(1)的个数等于0,且二维矩阵还仅有一个格子未走过(0),即代表通关,打印路径} else if (NotZero(m) == 0 && Zero(arr) == 1){arr[i][j] = 1;map[i][j] = '●';System.out.println("-----------------------");System.out.println("7.路线图如下:");PrintArray(map);System.out.println("Count:" + (++count));//如果m中可选择(1)的个数等于0,代表走到死胡同,清空二维数组和map数组,重新开始}else if (NotZero(m) == 0 || isNotChoice(arr)){System.out.println("Count:" + (++count));System.out.println("-----------------------");System.out.println("重新开始:");char[][] mapRestart = SetArray(matrix, blank);mapRestart[start[0]][start[1]] = '◎';ClearArray(arr);SetArray(arr, blank);Start(arr, start[0], start[1], mapRestart, blank, start, matrix, count);}}}

注:map[][]是复刻arr[][]二维数组的字符数组,方便观察程序的运行情况

四、演示(OneStrokeV1.0)

以上面的第15关为例,开始演示:

第一步:输入矩阵的行列数

注:第15关是4×4的矩阵,输入:4 4

第二步:输入无效矩阵ID

注:第15关,按照行编号,从0开始,ID为0、2、3、12的格子是无效的,故输入:0 2 3 12

第三步:输入游戏起点坐标

注:从0开始,15关的起点是,第0行,第1列,输入:0 1

运行结果

注:可以看出,通关的路线打印出来了。走了两次错误的路径,Count的值为2,在第三次的时候通关了。

五、有待改进

沿着算法的设计思路下来,不难发现以下几点有待改进的地方:

1.不符合算法的有穷性,可能无法得到通关的路径

算法的有穷性是指算法必须能在执行有限个步骤之后终止;很明显,因为此算法没有排除错误路径的机制,所以按道理来说,运气足够好或者运气足够差,程序都是有可能一直走错误的路径,还可能是重复的。程序之所以能运行出通关路径,是因为15关的矩阵较为简单,路径的变化比较少。如演示的15关,我列举了一下,一共有26种路径变化(如图),其中有4种通关路径,也就是说,程序运行一次得出结果的概率为2/13,理论上运行时间足够长,尝试的次数足够多,是大概率能得出结果的,但实际上也可能一直得不出结果,这就不符合上面说的算法有穷性的“必须能在执行有限个步骤之后终止”,这个算法做不到“必须”,只能做到2/13

2.空间和时间复杂度高,IDEA的资源开销非常大

运行一下6×6的矩阵试一下,问题一下就会暴露出来。如117这关,运行之后,IDEA报错


这是IDEA中java虚拟机中的线程的栈内存太小,满足不了程序递归的层数了,所以报错了。解决这个问题需要设置程序配置中的这个参数,“-Xss128m”,上面这一关我是设置128MB的,如下,运行说明还是不够,可以再调高一点,但这治标不治本。

总结

通过穷举法,先大致码出一个程序,视为V1.0,再分析程序中的问题和改进的方法。需要全部源码的请留言

【一笔画完】通关路径算法的Java代码实现V1.0相关推荐

  1. 负载均衡算法及其Java代码实现

    负载均衡算法及其Java代码实现 什么是负载均衡 负载均衡,英文 名称为Load Balance,指由多台服务器以对称的方式组成一个服务器集合,每台服务器都具有等价的地位,都可以单独对外提供服务而无须 ...

  2. 对一致性Hash算法,Java代码实现的深入研究

    一致性Hash算法 关于一致性Hash算法,在我之前的博文中已经有多次提到了,MemCache超详细解读一文中"一致性Hash算法"部分,对于为什么要使用一致性Hash算法.一致性 ...

  3. java负载均衡原理_多种负载均衡算法及其 Java 代码实现

    首先给我们介绍下什么是负载均衡 负载均衡 树立在现有网络结构之上,它供给了一种廉价有用通明的办法扩展 网络设备和 效劳器的带宽.添加 吞吐量.加强网络数据处理才能.进步网络的灵敏性和可用性. 负载均衡 ...

  4. Java编码规范V1.0

     Java编码规范V1.0 1 代码总体原则 1. 清晰第一 清晰性是易于维护.易于重构的程序必需具备的特征.代码首先 是给人读的,其次才给机器用来执行. 目前软件维护期成本占整个生命周期成本的 40 ...

  5. Java自动化测试系列[v1.0.0][TestNG测试开发环境配置]

    基于之前写的一篇文章Java自动化测试系列[v1.0.0][Maven开发环境]的基础上,阐述如何配置单元测试框架TestNG的测试开发环境 创建Maven项目 启动IDEA,点击Create New ...

  6. 我的世界1 11java,Editing Java版Alpha v1.0.11

    Anti-spam check. Do not fill this in!{{version nav |title=Alpha v1.0.11 |edition=java |image=Alpha v ...

  7. 微信一笔画游戏 的 路径算法

    最近 地铁 上玩这个 然后想了想  路径 算法 只会穷举~ function findWay(m, n,/*空点 不能走*/emptyArrs,/*起点*/start) {let totalPoint ...

  8. 多种负载均算法及其 Java 代码实现 --转

    原文地址:https://www.oschina.net/news/81750/variety-pf-load-balancing-algorithm-and-its-java-code 首先给大家介 ...

  9. java 路由算法_几种简单的负载均衡算法及其Java代码实现

    什么是负载均衡 负载均衡,英文 名称为Load Balance,指由多台服务器以对称的方式组成一个服务器集合,每台服务器都具有等价的地位,都可以单独对外提供服务而无须其他服务器的辅助.通过某种 负载分 ...

最新文章

  1. keras 的 example 文件 babi_rnn.py 解析
  2. 以太坊是什么,为什么这么火?
  3. 【错误记录】NDK 配置错误 ( C/C++ debug|arm64-v8a : Could not get version from cmake.dir path )
  4. 010 Editor v8.0.1_x32分析以及注册机制作
  5. 【数据结构与算法】之深入解析“等差数列划分II”的求解思路与算法示例
  6. 二:java语法基础:
  7. MFC实现Windows锁屏
  8. linux nslookup命令安装,在CentOS中安装nslookup命令
  9. TikTok如何将粉丝转到私域,提高转化和复购?
  10. JavaBean递归拷贝工具类Dozer
  11. 基于Matlab的语音识别
  12. vb.net的socket编程
  13. 用Python读写Word文档入门
  14. flash 火狐总是崩溃_火狐浏览器flash插件崩溃怎么办?解决firefox经常出现Adobe Flash 插件已崩溃方法...
  15. word刷子刷格式_Word文档中用格式刷快速编辑数据格式的方法
  16. Marvell 88E1111 百兆工程 (FPGA)
  17. 在Delphi中很精确地控制生成的WORD文档的格式
  18. JavaScript中的数据类型判断
  19. 华为扩大内存代码_如何将华为手机带代码加大内存5s?
  20. vue中集成的ui组件库_Vue组件可使用Vault Flow通过Braintree集成PayPal付款

热门文章

  1. Qt之线程的开始暂停恢复停止
  2. vectorvn1610报价_Vector硬件VN1640A
  3. 计算机网络:常见的计网面试题整理(一)
  4. h5学习笔记:学习frozenui 的ui-row代码
  5. 微信小程序开发实战-第二周
  6. apk反编译工具及使用步骤(详解)
  7. 设计模式之禅-策略模式
  8. 【UDS统一诊断服务】(补充)五、ECU bootloader开发要点详解 (1)
  9. (转)图解Intel电脑组装过程
  10. 实现数组扁平化的 6 种方式