数据结构学习5-使用递归解决迷宫问题

迷宫问题

给定一张迷宫地图和一个迷宫入口,和出口 找到一条可以通过的道路
在这里插入图片描述

迷宫地图

我们使用一个二维数组代表迷宫,其中

  • 0 代表没有走过
  • 1 代表障碍物
  • 2表示路可以走
  • 3 标识已走过,但是不通
  • 9 代表终点

关键代码

  • go方法 根据执行的选择方向模式,探测下一步应该往哪走,当4个方向都不满足的时候,就需要对回滚到上一个操作点,这个地方使用到了栈,

  • goWithDynamic方法 根据当前点和终点的位置进行动态判定4个方向的优先级,但是针对于很多障碍物的地图,它表现的并没有想象中那么好,甚至有点差

完整代码
public class Maze {

    /**
     * 地图
     * 0 代表没有走过 1标识墙  2表示路可以走 3 标识已走过,但是不通
     * 9 终点
     */
    private int[][] map;

    /**
     * 初始位置
     */
    private int[] cur;

    /**
     * 终点的位置
     */
    private int[] end;

    /**
     * 用于计数
     */
    private int count;
    /**
     * 存放轨迹路径 用于回溯
     */
    private Stack<Integer[]> track;

    public Maze(int row, int col) {
        initMap(row, col);
        // 初始化一个轨迹路径
        track = new Stack<>();
    }

    private void initMap(int row, int col) {
        // 设置一个边框 辅助程序运行
        row += 2;
        col += 2;
        map = new int[row][col];
        // 初始化墙
        for (int i = 0; i < col; i++) {
            map[0][i] = 1;
            map[row - 1][i] = 1;
        }
        for (int i = 0; i < row; i++) {
            map[i][0] = 1;
            map[i][col - 1] = 1;
        }
    }

    /**
     * 设置栅栏
     */
    public Maze setBarrier(int row, int col) {
        map[row][col] = 1;
        return this;
    }

    public void print() {
        // 将迷宫输出
        System.out.println("地图:");
        for (int i = 0; i < map.length; i++) {
            for (int j = 0; j < map[i].length; j++) {
                System.out.printf("\t%s", map[i][j]);
            }
            System.out.print("\n");
        }
    }

    public Maze start(int row, int col) {
        if (map[row][col] != 0) {
            throw new RuntimeException("起点坐标设置有误");
        }
        // 起点默认走了
        map[row][col] = 2;
        cur = new int[]{row, col};
        return this;
    }

    /**
     * 设置终点
     */
    public Maze end(int row, int col) {
        // 检查终点位置
        if (map[row][col] != 0) {
            throw new RuntimeException("终点坐标设置有误");
        }
        end = new int[]{row, col};
        map[row][col] = 9;
        return this;
    }


    /**
     * 开始运行
     */
    public void go(int goMode) {
        // 计数
        count++;
        boolean result = false;
        if (goMode == 1) {
            // 顺时针
            result = goWithClockwise();
        } else if (goMode == 2) {
            // 逆时针
            result = goWithAnticlockwise();
        } else if (goMode == 3) {
            result = goWithDynamic();
        }
        if (!result) {
            // 回溯
            if (track.isEmpty()) {
                throw new RuntimeException("当前位置已不可达,需要重新规划路线");
            } else {
                System.out.println("执行回溯....");
                // 标记当前点为可达但是道路不通
                map[cur[0]][cur[1]] = 3;
                Integer[] pop = track.pop();
                cur[0] = pop[0];
                cur[1] = pop[1];
                System.out.printf("当前位置:(%d,%d)\n", cur[0], cur[1]);
            }
        }
        // 打印
        print();
        // 继续往下走
        if (map[cur[0]][cur[1]] != 9) {
            go(goMode);
        } else {
            // 终点入栈
            track.push(new Integer[]{cur[0], cur[1]});
            System.out.println("抵达终点...共执行:" + count + "次,实际路径长度:" + track.size());
            // 打印轨迹
            StringBuilder resultTrack = new StringBuilder();
            while (!track.isEmpty()) {
                Integer[] pop = track.pop();
                resultTrack.append("(" + pop[0] + "," + pop[1] + ")").append(" <- ");
            }
            resultTrack.append("开始");
            System.out.println(resultTrack);
        }
    }

    /**
     * 顺时针的走 上右下左
     */
    private boolean goWithClockwise() {
        return goTo(new int[]{1, 2, 3, 4});
    }


    /**
     * 逆时针走 下 右 上 左
     */
    private boolean goWithAnticlockwise() {
        return goTo(new int[]{3, 2, 1, 4});
    }

    /**
     * 根据当前的位置和终点的位置 动态计算4个方向的顺序
     * 每次都会重新计算该怎么走
     *
     * @return
     */
    private boolean goWithDynamic() {
        int[] dir; // 1 2 3 4 上右下左
        if (cur[0] < end[0]) {
            // 终点在下半局
            if (cur[1] < end[1]) {
                // 终点在右边
                dir = new int[]{2, 3, 1, 4};
            } else {
                // 左边
                dir = new int[]{4, 3, 1, 2};
            }
        } else {
            // 终点在上半局
            if (cur[1] < end[1]) {
                // 终点在右边
                dir = new int[]{1, 2, 3, 4};
            } else {
                // 左边
                dir = new int[]{1, 4, 2, 3};
            }
        }
        return goTo(dir);
    }

    /**
     * 按照指定顺序前往下一个点
     *
     * @param dir 1 2 3 4 上右下左
     * @return
     */
    private boolean goTo(int[] dir) {
        boolean result = false;
        for (int i = 0; i < 4; i++) {
            if (dir[i] == 1) {
                // 往上走
                result = goTo(-1, 0);
            } else if (dir[i] == 2) {
                // 往右边走
                result = goTo(0, 1);
            } else if (dir[i] == 3) {
                // 往下边走
                result = goTo(1, 0);
            } else {
                // 往左边走
                result = goTo(0, -1);
            }
            if (result) {
                return result;
            }
        }
        return result;
    }

    /**
     * 去下一个可达的点
     */
    private boolean goTo(int rowOffset, int colOffset) {
        int row = cur[0] + rowOffset, col = cur[1] + colOffset;
        boolean canGo = false;
        if (map[row][col] == 9) {
            canGo = true;
        } else if (map[row][col] == 0) {
            // 下个点没有走过
            map[row][col] = 2;
            canGo = true;
        }
        if (canGo) {
            // 当前位置入栈
            track.push(new Integer[]{cur[0], cur[1]});
            // 将下一个点的坐标作为新的位置
            cur[0] = row;
            cur[1] = col;
        }
        return canGo;
    }


    public static void main(String[] args) {
        Maze maze = new Maze(6, 5);
//        // 顺时针 (6,5) <- (5,5) <- (4,5) <- (3,5) <- (2,5) <- (1,5) <- (1,4) <- (1,3) <- (2,3) <- (3,3) <- (4,3) <- 开始
//        maze.setBarrier(3, 1).setBarrier(3, 2).start(4, 3).end(6, 5).go(1);
//
//
//        // 逆时针结果 (6,5) <- (6,4) <- (6,3) <- (5,3) <- (4,3) <- 开始
//        maze.setBarrier(3, 1).setBarrier(3, 2).start(4, 3).end(6, 5).go(2);

        // 测试回溯 使用逆时针 终点放在 1 1 上面
//        maze.setBarrier(3, 1).setBarrier(3, 2).start(4,3).end(1, 1).go(3);
        //  顺时针结果
        // (1,1) <- (2,1) <- (2,2) <- (1,2) <- (1,3) <- (2,3) <- (3,3) <- (4,3) <- 开始
        // 逆时针结果
        // (1,1) <- (2,1) <- (2,2) <- (1,2) <- (1,3) <- (2,3) <- (3,3) <- (3,4) <- (2,4) <- (1,4) <- (1,5) <- (2,5) <- (3,5) <- (4,5) <- (5,5) <- (6,5) <- (6,4) <- (6,3) <- (5,3) <- (4,3) <- 开始

        // 动态计算
        // (1,1) <- (1,2) <- (1,3) <- (2,3) <- (3,3) <- (4,3) <- 开始

        // 多设置一些栅栏 使用动态判断方向优先级的时候考虑不到栅栏存在的情况 因为对于判定方向优先级而言 它感知不到栅栏的存在
        maze
                .setBarrier(3, 1)
                .setBarrier(3, 2)
                .setBarrier(3, 3)
                .setBarrier(3, 4)
                .setBarrier(1,2)
                .setBarrier(5, 5)
                .start(4, 3).end(1, 1).go(3);

    }
}
使用递归需要遵守的重要守则

在这里插入图片描述

总结

回溯其实就是将每一步有效的操作都压入一个栈中,然后通过出栈和入栈实现对操作的撤销和重新执行。所以redo和undo 可以通过2个栈实现。基础知识还是很重要的,环环相扣,前面学习的数组可以作为队列的基石,数组和链表又可以用来实现栈,紧接着 栈又可以用来实现回溯算法。
所以以前跳过这些基础知识 直接看树,搞不懂是有道理的 。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值