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


前言

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

一、算法分析

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

第一点:游戏界面是一个矩阵,如游戏第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) {
            //根据传入的二维矩阵和坐标,计算该格子可选择的方向,返回方向选择集合m
            int[] 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,再分析程序中的问题和改进的方法。需要全部源码的请留言

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

何中应

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值