递归 迷宫 八皇后
递归
基本概念
递归就是方法自己调用自己,每次调用时传入不同的变量。递归有助于比那撑着解决复杂的问题,同时可以让代码变得简单
调用机制
打印问题思路分析:
递归调用规则:
- 当程序执行到一个方法时,就会开辟一个独立的空间 (栈)
- 每个空间的数据 (局部变量) ,是独立的;引用数据类型变量,是共享的
package com.atguigu.recursion;
/**
* @ClassName RecursionDemo
* @Author Jeri
* @Date 2022-02-17 19:34
* @Description 递归调用机制
*/
public class RecursionDemo {
//打印问题
public static void test(int n){
if(n > 2){
test(n - 1);
}
System.out.println("n = " + n);
}
//阶乘问题
public static int factorial(int n){
if(n == 1){
return 1;
}else{
return n * factorial(n-1);
}
}
public static void main(String[] args) {
//通过打印问题 回顾递归调用机制
test(4);
int res = factorial(4);
System.out.println("res = " + res);
}
}
n = 2
n = 3
n = 4
res = 24
使用场景
- 各种数学问题如: 8皇后问题 , 汉诺塔, 阶乘问题, 迷宫问题, 球和篮子的问题(google 编程大赛)
- 各种算法中也会使用到递归,比如快排,归并排序,二分查找,分治算法等
- 将用栈解决的问题–>递归代码比较简洁
重要规则
- 执行一个方法时,就创建一个新的受保护的独立空间(栈空间)
- 方法的局部变量是独立的,不会相互影响
- 如果方法中使用的是引用类型变量(比如数组),就会共享该引用类型的数据
- 递归必须向退出递归的条件逼近,否则就是无限递归,出现 StackOverflowError,死龟了:)
- 当一个方法执行完毕,或者遇到 return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法从栈中除去
迷宫问题
问题描述: 红色部分为障碍,小球需要从左上角移动到右下角,请给出一条可以移动的路径
**思路分析:**自己写不出来 ,递归回溯不知道应该如何描述
- 下一个位置寻路和当前位置寻路采用相同的策略,所以使用递归
代码实现:
package com.atguigu.recursion;
/**
* @ClassName Maze
* @Author Jeri
* @Date 2022-02-17 20:00
* @Description 迷宫
*/
public class Maze {
public static int[][] maze;
public static int rows;
public static int lines;
/*
* @Description 设置迷宫信息
* @Date 2022/2/17 20:59
* @param rows 迷宫行数
* @param lines 迷宫列数
**/
public static void setMaze(int rows,int lines){
//创建二维数组 模拟迷宫
maze = new int[rows][lines];
//迷宫数字状态 1:障碍 0:初始状态 2:此路通 3:此路不通
//迷宫设置障碍 上下两行
for(int i = 0;i < 7;i++){
maze[0][i] = 1;
maze[7][i] = 1;
}
//左右两列
for(int i = 0;i < 8;i++){
maze[i][0] = 1;
maze[i][6] = 1;
}
//特殊点
maze[3][1] = 1;
maze[3][2] = 1;
}
/*
* @Description 返回迷宫信息
* @Date 2022/2/17 20:59
**/
public static void getMaze(){
//输出地图
System.out.println("迷宫的情况:");
for(int i = 0;i < rows;i++){
for(int j = 0;j < lines;j++){
System.out.printf(maze[i][j] + " ");
}
System.out.println();
}
}
/*
* @Description 寻路策略
* @Date 2022/2/17 21:01
* @param i 横坐标
* @param j 纵坐标
* maze[i][j] 表示迷宫的出发点
* 如果能够到达maze[6][5] 则说明通路找到
* 约定:迷宫数字状态 1:障碍 0:初始状态 2:此路通 3:此路不通
* 寻路时 需要自定义寻路策略 不同的寻路策略可能得到不同的路径
* @return maze[i][j]是否可通
**/
public static boolean setWay(int i,int j){
if(maze[6][5] == 2){
//最后一个节点为通
return true;
}else{
//小球开始从节点 maze[i][j]开始寻路
if(maze[i][j] == 0){
//0:初始状态 当前节点还没有走过
//设置寻路策略 下 右 上 左
//假设该点可以走通
maze[i][j] = 2;
if(setWay(i + 1,j)){//下
return true;
}else if(setWay(i,j + 1)){//右
return true;
}else if(setWay(i - 1 ,j)){//上
return true;
}else if(setWay(i,j - 1)){//左
return true;
}else{
//该点是死路
maze[i][j] = 3;
return false;
}
}else{
// maze[i][j] != 0 1 2 3 都不能过这一点
return false;
}
}
}
public static void main(String[] args) {
//查看地图情况
rows = 8;
lines = 7;
setMaze(rows,lines);
getMaze();
//使用递归寻路
setWay(1,1);
//获得迷宫信息
//将所有数字为2的点连起来 即为路径
System.out.println();
System.out.println("将所有数字为2的点连起来 即为路径");
getMaze();
}
}
迷宫的情况:
1 1 1 1 1 1 1
1 0 0 0 0 0 1
1 0 0 0 0 0 1
1 1 1 0 0 0 1
1 0 0 0 0 0 1
1 0 0 0 0 0 1
1 0 0 0 0 0 1
1 1 1 1 1 1 1
将所有数字为2的点连起来 即为路径
迷宫的情况:
1 1 1 1 1 1 1
1 2 0 0 0 0 1
1 2 2 2 0 0 1
1 1 1 2 0 0 1
1 0 0 2 0 0 1
1 0 0 2 0 0 1
1 0 0 2 2 2 1
1 1 1 1 1 1 1
八皇后
**问题描述:**八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848 年提出:在 8×8 格的国际象棋上摆放八个皇后,使其不能互相攻击,即:任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法(92)。
思路分析:
- 第一个皇后先放第一行第一列
- 第二个皇后放在第二行第一列、然后判断是否 OK, 如果不 OK,继续放在第二列、第三列、依次把所有列都
放完,找到一个合适 - 继续第三个皇后,还是第一列、第二列……直到第 8 个皇后也能放在一个不冲突的位置,算是找到了一个正确
解 - 当得到一个正确解时,在栈回退到上一个栈时,就会开始回溯,即将第一个皇后,放到第一列的所有正确解,
全部得到 - 然后回头继续第一个皇后放第二列,后面继续循环执行 1,2,3,4 的步骤
**说明:**理论上应该创建一个二维数组来表示棋盘,但是实际上可以通过算法,用一个一维数组即可解决问题. arr[8] =
{0 , 4, 7, 5, 2, 6, 1, 3}
- arr[i] 下标i 表示第 i 行,即第 i 个皇后,(注:皇后编号从 0-7)
- arr[i] = value ,value 表示第 i 个皇后放在 第 value 列 (注:皇后摆放位置的列从0-7)
注:实际上采用暴力解法,类似于8重 for 循环来解。 采用递归算法使得代码变得简洁,理解起来及其抽象。递归回溯相当于记录 当前一层 for 循环的检查点 checkpoint ,多debug一下吧
package com.atguigu.recursion;
/**
* @ClassName Queen8
* @Author Jeri
* @Date 2022-02-17 22:15
* @Description 八皇后问题
*/
public class Queen8 {
//max 表示皇后数量
public static int max;
//arr表示皇后存放的位置
public static int[] arr;
//count 表示解法总数
public static int count = 0;
private void check(int n){
if(n == max){
print();
return;
}
//依次放入皇后 判断是否冲突
for(int i = 0;i < max;i++){
//当前皇后 先放在第一列
arr[n] = i;
//判断第n个皇后到第i列时 是否冲突
if(!isConflict(n)){
//不冲突 放置下一个皇后
check(n+1);
}
//冲突 for 循环继续执行 arr[n] = i
//当前皇后位置后移
}
}
/*
* @Description 检测第n个皇后是否与前n-1个皇后冲突
* @Date 2022/2/17 22:46
* @param n 第n个皇后
* @return [n] false 不冲突
*/
private boolean isConflict(int n){
for(int i = 0;i < n;i++){
//皇后冲突原则:任意两个皇后都不能处于同一行、同一列或同一斜线上
//1.同一行 一维数组表示后 任意两个皇后不能处于用一行
//2.同一列 arr[i] == arr[n]
//3.同一斜线 abs(纵坐标 / 横坐标) = 1
// Math.abs(n-i) == Math.abs(arr[n] - arr[i])
if(arr[i] == arr[n] || Math.abs(n-i) == Math.abs(arr[n] - arr[i])){
return true;
}
}
return false;
}
/*
* @Description 输出皇后的摆放的位置
* @Date 2022/2/17 22:16
**/
private void print(){
count++;
for(int i = 0; i < arr.length;i++){
System.out.printf(arr[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
//max 表示皇后数量
max = 8;
//arr表示皇后存放的位置
arr = new int[max];
Queen8 queen8 = new Queen8();
queen8.check(0);
System.out.printf("总共有%d种解法\n",count);
}
}
0 4 7 5 2 6 1 3
0 5 7 2 6 3 1 4
0 6 3 5 7 1 4 2
0 6 4 7 1 3 5 2
1 3 5 7 2 0 6 4
1 4 6 0 2 7 5 3
1 4 6 3 0 7 5 2
1 5 0 6 3 7 2 4
1 5 7 2 0 3 6 4
1 6 2 5 7 4 0 3
1 6 4 7 0 3 5 2
1 7 5 0 2 4 6 3
2 0 6 4 7 1 3 5
2 4 1 7 0 6 3 5
2 4 1 7 5 3 6 0
2 4 6 0 3 1 7 5
2 4 7 3 0 6 1 5
2 5 1 4 7 0 6 3
2 5 1 6 0 3 7 4
2 5 1 6 4 0 7 3
2 5 3 0 7 4 6 1
2 5 3 1 7 4 6 0
2 5 7 0 3 6 4 1
2 5 7 0 4 6 1 3
2 5 7 1 3 0 6 4
2 6 1 7 4 0 3 5
2 6 1 7 5 3 0 4
2 7 3 6 0 5 1 4
3 0 4 7 1 6 2 5
3 0 4 7 5 2 6 1
3 1 4 7 5 0 2 6
3 1 6 2 5 7 0 4
3 1 6 2 5 7 4 0
3 1 6 4 0 7 5 2
3 1 7 4 6 0 2 5
3 1 7 5 0 2 4 6
3 5 0 4 1 7 2 6
3 5 7 1 6 0 2 4
3 5 7 2 0 6 4 1
3 6 0 7 4 1 5 2
3 6 2 7 1 4 0 5
3 6 4 1 5 0 2 7
3 6 4 2 0 5 7 1
3 7 0 2 5 1 6 4
3 7 0 4 6 1 5 2
3 7 4 2 0 6 1 5
4 0 3 5 7 1 6 2
4 0 7 3 1 6 2 5
4 0 7 5 2 6 1 3
4 1 3 5 7 2 0 6
4 1 3 6 2 7 5 0
4 1 5 0 6 3 7 2
4 1 7 0 3 6 2 5
4 2 0 5 7 1 3 6
4 2 0 6 1 7 5 3
4 2 7 3 6 0 5 1
4 6 0 2 7 5 3 1
4 6 0 3 1 7 5 2
4 6 1 3 7 0 2 5
4 6 1 5 2 0 3 7
4 6 1 5 2 0 7 3
4 6 3 0 2 7 5 1
4 7 3 0 2 5 1 6
4 7 3 0 6 1 5 2
5 0 4 1 7 2 6 3
5 1 6 0 2 4 7 3
5 1 6 0 3 7 4 2
5 2 0 6 4 7 1 3
5 2 0 7 3 1 6 4
5 2 0 7 4 1 3 6
5 2 4 6 0 3 1 7
5 2 4 7 0 3 1 6
5 2 6 1 3 7 0 4
5 2 6 1 7 4 0 3
5 2 6 3 0 7 1 4
5 3 0 4 7 1 6 2
5 3 1 7 4 6 0 2
5 3 6 0 2 4 1 7
5 3 6 0 7 1 4 2
5 7 1 3 0 6 4 2
6 0 2 7 5 3 1 4
6 1 3 0 7 4 2 5
6 1 5 2 0 3 7 4
6 2 0 5 7 4 1 3
6 2 7 1 4 0 5 3
6 3 1 4 7 0 2 5
6 3 1 7 5 0 2 4
6 4 2 0 5 7 1 3
7 1 3 0 6 4 2 5
7 1 4 2 0 6 3 5
7 2 0 5 1 4 6 3
7 3 0 2 5 1 6 4
总共有92种解法
参考文献
尚硅谷Java数据结构与java算法