华容道问题

此问题原网页:

http://mp.weixin.qq.com/s?__biz=MzI2NjA3NTc4Ng==&mid=2652080483&idx=1&sn=4519d9b087629dece61d1700a086cc20&chksm=f1748286c6030b90512d5d1c8a8b91e4b1c36d6b8d8c73673da90301a194f3e6c5e6873c7bf4&mpshare=1&scene=23&srcid=1015K7EoO4fdiP0BYoBIdWGg#rd

在此链接程序基础上进行了一些改造,以下程序可以直接运行,分别采用了深度优先搜索和广度优先搜索来解决这个问题。

仅以此博客记录,同时供有兴趣者参考。

*************************************************我是源码分界线(全盘复制即可运行,注意主类名对应)*******************************

import java.util.*;

/*
* 华容道深度优先搜索解法
* 一条路走到黑,能找出路径(是不是最佳难以保证,大多数不是最佳)
* */

class HuaRongDao {

    //方向定义
    private static final int UP = 1;
    private static final int DOWN = 2;
    private static final int LEFT = 3;
    private static final int RIGHT = 4;
    //终点状态定义
    private static final Integer WIN_STATE = 123456780;
    //保存已经搜索过的状态
    private Set<Integer>  statusSet = new HashSet<Integer>();

    private int [][]arr;
    //空格位置
    private int x;
    private int y;

    //定义移动的数组
    private List<Integer> moveArr = new LinkedList<Integer>();   //java双向链表,可用做栈

    //给定华容道初始值,并遍历找出空格位置(构造方法)
    public HuaRongDao(){
        arr = new int[3][3];
        System.out.println("请按照顺序依次输入3*3华容道初始值(0到8且数字不可重复):");
        Scanner sc = new Scanner(System.in);
        //键盘录入华容道初始值,对重复录入进行判断,并记录空格位置
        for(int i=0;i<arr.length;i++){
            for(int j=0;j<arr.length;j++){
                arr[i][j] = sc.nextInt();
                //检验是否重复输入
                for(int l=0;l<=i;l++){
                    for(int m=0;m<j;m++){     //检验到前一个
                        if(arr[l][m]==arr[i][j]){
                            System.out.println("输入重复,请重新输入\n");
                            arr[i][j] = sc.nextInt();
                        }
                    }
                    //记录空格位置
                    if(arr[i][j]==0){
                        x = i;
                        y = j;
                    }
                }

            }
        }
    }

    //判断能够移动的方向
    private boolean canMove(int direction){
        switch(direction){
            //x>0才能上移
            case UP:
                return x > 0;
            case DOWN:
                return x < 2;
            case LEFT:
                return y > 0;
            case RIGHT:
                return y < 2;
        }
        return false;
    }

    //移动函数,该函数不做判断,调用前请先调用能否移动函数查看返回结果
    private void move (int direction){
        int temp;
        switch(direction){
            case UP:
                temp = arr[x-1][y];
                arr[x-1][y] = arr[x][y];
                arr[x][y] = temp;
                x = x-1;//存储当前空格位置
                break;
            case DOWN:
                temp = arr[x+1][y];
                arr[x+1][y] = arr[x][y];
                arr[x][y] = temp;
                x = x+1;
                break;
            case LEFT:
                temp = arr[x][y-1];
                arr[x][y-1] = arr[x][y];
                arr[x][y] = temp;
                y = y-1 ;//存储当前空格位置
                break;
            case RIGHT:
                temp = arr[x][y+1];
                arr[x][y+1] = arr[x][y];
                arr[x][y] = temp;
                y = y+1;//存储当前空格位置
                break;
        }
        moveArr.add(direction);//用链表来存储移动步骤

    }
    // 某个方向的回退,该函数不作判断,直接移动
    // 其操作和move方法正好相反,上方向的回退其实正好是向下移动,其他类推
    private void moveBack(int direction) {
        int temp;
        switch (direction) {
            case UP:
                temp = arr[x+1][y];
                arr[x+1][y] = arr[x][y];
                arr[x][y] = temp;
                x= x+1;
                break;
            case DOWN:
                temp = arr[x-1][y];
                arr[x-1][y] = arr[x][y];
                arr[x][y] = temp;
                x = x-1;//存储当前空格位置
                break;
            // 空格和左侧数字交换
            case LEFT:
                temp = arr[x][y+1];
                arr[x][y+1] = arr[x][y];
                arr[x][y] = temp;
                y = y+1;//存储当前空格位置
                break;
            // 空格和右侧数字交换
            case RIGHT:
                temp = arr[x][y-1];
                arr[x][y-1] = arr[x][y];
                arr[x][y] = temp;
                y = y-1;//存储当前空格位置
                break;

        }
        // 记录的移动步骤出栈
        moveArr.remove(moveArr.size() - 1);
    }



    //获取当前状态,将华容道当前数字按顺序组成一个大数字代表其状态
    private int getStatus(){
        int status = 0;
        for(int i=0; i<arr.length; i++) {
            for(int j=0; j<arr.length; j++) {
                status = status * 10 + arr[i][j];    //每次都将新加入的数字作为个位数
            }
        }
        return status;
    }

    //打印当前华容道可视化状态
    private void printHuaRongDao() {
        System.out.println("华容道当前状态:");
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                System.out.print(arr[i][j] + "\t");
            }
            System.out.println();
        }
    }

    // 打印路径
    private void printRoute() {
        for(int i=0; i<moveArr.size(); i++) {
            System.out.print(getDirString(moveArr.get(i)));
            System.out.print(" ");
        }
    }

    // 方向与其对应的字符串
    private String getDirString(int dir) {
        switch (dir) {
            case LEFT:
                return "左";
            case RIGHT:
                return "右";
            case UP:
                return "上";
            case DOWN:
                return "下";
        }
        return null;
    }

    //搜索方法
    private boolean search(int direction){
        if(moveArr.size()<10000) {
            //判断是否可以向该方向走
            if (canMove(direction)) {
                //向该方向移动
                move(direction);
                //移动后的状态
                Integer status = getStatus();
                //如果是胜利状态,返回true
                if (WIN_STATE.equals(status)) {
                    printHuaRongDao();
                    System.out.println("移动路径为:");
                    printRoute();
                    return true;
                }
                //如果是之前走过的状态了,返回false(这一步是判断是否是之前走过的返回false)
                if (statusSet.contains(status)) {
                    moveBack(direction);
                    return false;
                }
                //将当前状态存入set
                statusSet.add(status);
                printHuaRongDao();
                //继续向四个方向进行搜索
                boolean searchFourok = search(UP) || search(DOWN) || search(LEFT) || search(RIGHT);
                if (searchFourok) {
                    return true;
                } else {
                    //这一步走错了,回退(而这一步是在没有重复得前提下,继续走但是没有成功,持续回退)
                    moveBack(direction);
                    return false;
                }
            }
        }
        else{
            return false;
        }
        return  false;
    }

    //解题入口方法
    public boolean solve(){
        Integer status = getStatus();
        if(WIN_STATE.equals(status)){
            printHuaRongDao();
            System.out.println("移动路径为:");
            printRoute();
            return true;

        }
        printHuaRongDao();
        //初始状态记录
        statusSet.add(status);
        //再向四个方向搜索
        return search(UP)||search(DOWN)||search(LEFT)||search(RIGHT);

    }
}
public class TestHuaRongDao{
    public static void main(String[] args){
        HuaRongDao hrd = new HuaRongDao();
        hrd.solve();
    }
}

 

import java.util.*;

/*
* 华容道广度优先搜索解法
* 只能找出最佳路径
* */
class HuaRongDao2{
    //方向定义
    private static final int UP = 0;
    private static final int DOWN = 1;
    private static final int LEFT = 2;
    private static final int RIGHT = 3;
    //终点状态定义
    private static final Integer WIN_STATE = 123456780;
    /* 定义辅助数组
    二维数组,相当于dxdy[4][2],四个数对代表四个方向,方向数值与二维数组第一维相对应,dxdy[0][0] = -1;dxdy[0][1] = 0;dxdy[1][0] = 1;dxdy[1][1] = 0 ........
    */
    private static final int[][] dxdy = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

    // 3x3的九宫格
    private int[][] arr;

    // 记录空格的位置
    private int x;
    private int y;

    // 定义移动的数组
    private List<Integer> moveArr = new LinkedList<Integer>();
    // 保存已经搜索过的状态
    private Set<Integer> statusSet = new HashSet<Integer>();


    // 代表广搜的每一步,通过lastItem链起来,记录每一步的状态和移动方向并保存上一步的同样的信息
    private class SearchItem {
        private Integer status;
        private Integer dir;
        private SearchItem lastItem;
        SearchItem(Integer status, Integer dir, SearchItem lastItem) {
            this.status = status;
            this.dir = dir;
            this.lastItem = lastItem;
        }
        public Integer getStatus() {return status;}
        public Integer getDir() {return dir;}
        public SearchItem getLastItem() {return lastItem;}
    }

    // 广搜的存储队列
    private List<SearchItem> statusToSearch = new LinkedList<SearchItem>();

    //给定华容道初始值,并遍历找出空格位置(构造方法)
    public HuaRongDao2(){
        arr = new int[3][3];
        System.out.println("请按照顺序依次输入3*3华容道初始值(0到8且数字不可重复):");
        Scanner sc = new Scanner(System.in);
        //键盘录入华容道初始值,对重复录入进行判断,并记录空格位置
        for(int i=0;i<arr.length;i++){
            for(int j=0;j<arr.length;j++){
                arr[i][j] = sc.nextInt();
                //检验是否重复输入
                for(int l=0;l<=i;l++){
                    for(int m=0;m<j;m++){     //检验到前一个
                        if(arr[l][m]==arr[i][j]){
                            System.out.println("输入重复,请重新输入\n");
                            arr[i][j] = sc.nextInt();
                        }
                    }
                    //记录空格位置
                    if(arr[i][j]==0){
                        x = i;
                        y = j;
                    }
                }

            }
        }
    }

    // 判断是否可以朝某个方向进行移动
    private boolean canMove(int direction) {
        switch (direction) {
            // y > 0才能左移
            case LEFT:
                return y > 0;
            // y < 2才能右移
            case RIGHT:
                return y < 2;
            // x > 0才能上移
            case UP:
                return x > 0;
            // x < 2才能下移
            case DOWN:
                return x < 2;
        }
        return false;
    }

    // 找出该方向的相反方向
    private int getBackDir(int direction) {
        switch (direction) {
            // y > 0才能左移
            case LEFT:
                return RIGHT;
            // y < 2才能右移
            case RIGHT:
                return LEFT;
            // x > 0才能上移
            case UP:
                return DOWN;
            // x < 2才能下移
            case DOWN:
                return UP;
        }
        return 0;
    }

    // 朝某个方向进行移动,该函数不作判断,直接移动
    // 调用前请自行用canMove先行判断
    private void move(int direction) {
        int temp;
        temp = arr[x + dxdy[direction][0]][y + dxdy[direction][1]];   //方向与第一维数组位置相对应,该位置的数对代表x和y值相应变化,通过加法来实现。注意区分数组位置的0,1,2,3和方向数值的0,1,2,3
        arr[x + dxdy[direction][0]][y + dxdy[direction][1]] = 0;      //也可用 arr[x + dxdy[direction][0]][y + dxdy[direction][1]] = arr[x][y]
        arr[x][y] = temp;
        x = x + dxdy[direction][0];
        y = y + dxdy[direction][1];
    }

    // 某个方向的前进,该函数不作判断,直接移动
    private void moveForward(int direction) {
        move(direction);
        // 该方向记录
        //moveArr.add(direction);                 //步骤记录已经由item记录了,这步看不出有什么意义???
    }

    // 某个方向的回退,该函数不作判断,直接移动
    // 其操作和moveForward方法正好相反
    private void moveBack(int direction) {
        move(getBackDir(direction));
        // 记录的移动步骤出栈
        //moveArr.remove(moveArr.size() - 1);      //步骤记录已经由item记录了,这步看不出有什么意义???
    }

    // 获取状态,这里把9个数字按顺序组成一个整数来代表状态
    // 方法不唯一,只要能区分九宫格状态就行
    public Integer getStatus() {
        int status = 0;
        for(int i=0; i<arr.length; i++) {
            for(int j=0; j<arr.length; j++) {
                status = status * 10 + arr[i][j];
            }
        }
        return status;
    }

    // 根据状态还原九宫格数组
    // 该方法是getStatus的逆过程,感觉没必要用这个方法
    public void recoverStatus(Integer status) {
        for(int i=0; i<arr.length; i++) {
            for(int j=0; j<arr.length; j++) {
                arr[2 - i][2 - j] = status % 10;   //逆序赋值
                status = status / 10;
            }
        }
        getXY();
    }

    // 获取空格的x和y坐标
    private void getXY() {
        for(int i=0; i<arr.length; i++) {
            for(int j=0; j<arr.length; j++) {
                if(arr[i][j] == 0) {
                    x = i;
                    y = j;
                }
            }
        }
    }

    // 搜索方法
    private boolean search() {
        // 如果还有要搜索的状态
        while(statusToSearch.size() > 0) {
            // 队首出列
            SearchItem item = statusToSearch.remove(0);
            Integer status = item.getStatus();
            // 搜到了
            if(status.equals(WIN_STATE)) {
                // 找到路径
                recordRoute(item);
                printRoute();
                return true;
            }
            // 根据status还原arr和x,y,不过感觉不到这个方法的意义,画蛇添足
            recoverStatus(status);
            // 4个方向进行遍历
            for(int i=0; i<4; i++) {
                // 如果能够朝该方向行走
                if(canMove(i)) {
                    // 向前一步
                    moveForward(i);
                    status = getStatus();
                    // 之前搜过的状态
                    if (statusSet.contains(status)) {
                        moveBack(i);
                        // 放弃
                        continue;   //跳出本次循环,并开始执行下次循环
                    }
                    // 新状态加入待搜索
                    statusSet.add(status);
                    statusToSearch.add(new SearchItem(status, i, item));  //每一步存储节点的信息包含本步的状态以及上一步的节点信息
                    moveBack(i);
                }
            }
        }
        return false;
    }


    // 根据链表顺藤摸瓜,找到路径
    private void recordRoute(SearchItem item) {
        while(null != item.getLastItem()) {
            moveArr.add(0, item.getDir());
            item = item.getLastItem();
        }
    }

    // 打印路径
    public void printRoute() {
        for(int i=0; i<moveArr.size(); i++) {
            System.out.print(getDirString(moveArr.get(i)));
            System.out.print(" ");
        }
    }

    // 方向与其对应的字符串
    private String getDirString(int dir) {
        switch (dir) {
            case LEFT:
                return "左";
            case RIGHT:
                return "右";
            case UP:
                return "上";
            case DOWN:
                return "下";
        }
        return null;
    }

    // 打印当前华容道的状态
    private void print() {
        for(int i=0; i<arr.length; i++) {
            for(int j=0; j<arr.length; j++) {
                System.out.print(arr[i][j]);
                System.out.print(" ");
            }
            System.out.println();
        }
    }

    // 解题入口方法
    public boolean solve() {
        Integer status = getStatus();
        // 如果已经是胜利状态,返回true
        if(WIN_STATE.equals(status)) {
            return true;
        }
        // 初始状态先记录
        statusSet.add(status);
        // 初始状态进入搜索队列
        statusToSearch.add(new SearchItem(status, null, null));
        return search();
    }

}
public class TsetHuaRongDao2 {
    public static void main(String[] args){
        HuaRongDao2 hrd = new HuaRongDao2();
        hrd.solve();
    }

}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值