1、概念
递归就是方法自己调用自己,每次调用时传入不同的变量.递归有助于编程者解决复杂的问题,同时可以让代码变得简洁。并且递归用到了虚拟机栈
2、能解决的问题
数学问题
- 八皇后问题
- 汉诺塔
- 求阶乘
- 迷宫问题
- 球和篮子
3、规则
- 方法的变量是独立的,不会相互影响的
- 如果方法中使用的是引用类型变量(比如数组),就会共享该引用类型的数据
- 递归必须向退出递归的条件逼近,否则就是无限递归,出现 StackOverflowError
- 当一个方法执行完毕,或者遇到 return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕
4、迷宫问题
思路
- 用一个二维矩阵代表地图
- 1代表边界
- 0代表未做过该地点
- 2代表走过且能走得通
- 3代表走过但走不通
- 设置起点和终点以及每个地点的行走策略
- 行走策略指在该点所走的方向的顺序,如 右->下->左->上(调用寻找路径的方法,使用递归)
- 每次行走时假设该点能够走通,然后按照策略去判断,如果所有策略判断后都走不通,则该点走不通
图解
代码
package com.luyi.DataStructures.recursion;import com.sun.org.apache.xml.internal.resolver.helpers.PublicId;public class MiGong {public static void main(String[] args) {// 创建一个二维数组 模拟迷宫int[][] map = new int[8][7];// 使用1表示墙// 上下置位1for (int i = 0; i < 7; i++ ) {map[0][i] = 1;map[7][i] = 1;}//左右置位1for (int i = 0; i < 8; i++ ) {map[i][0] = 1;map[i][6] = 1;}//// 设置挡板 1 表示map[3][1] = 1;map[3][2] = 1;//map[1][2] = 1;//map[2][2] = 1;// 输出地图System.out.println("输出地图");for (int i = 0; i < 8; i++) {for (int j = 0; j < 7; j++) {System.out.print(map[i][j] + " ");}System.out.println();}// 使用递归回溯 找路 map是引用类型 可以动态改变值 等于公共变量if(setWay(map, 1, 1)){// 输出新的地图System.out.println("小球的路径");for (int i = 0; i < 8; i++) {for (int j = 0; j < 7; j++) {System.out.print(map[i][j] + " ");}System.out.println();}}else {System.out.println("无法找到");}}// 使用递归回溯给小球找路// 说明 map是地图// i,j表示从哪个位置开始出发(i,j)// 如果小球到map[6][5] = 则说明通路找到// 约定 当map[i][j] 为0时 表示该点没有走过 1为墙 2为通路(小球行径路径) 3为该位置已经走过 但是走不通// 再走迷宫时 需要确定一个策略(行径方向 当走不通时往策略的下一个方向) 下->右->上->左 ,如果该点都走不通 在回溯/**** @param map 地图* @param i 从哪个位置开始找* @param j* @return 如果找到通路 返回true 否则为false*/public static boolean setWay(int[][] map, int i, int j) {if (map[6][5] == 2){// 说明通路已经找到return true;}else {//如果当前的点还没有走过if (map[i][j] == 0){// 按照上述策略 下->右->上->左尝试map[i][j] = 2; // 假的当前点可以走if (setWay(map, i + 1, j )){ // 向下走return true;}else if (setWay(map,i , j + 1)){ // 向右走return true;}else if (setWay(map, i - 1, j)){ // 向上走return true;}else if (setWay(map, i,j - 1)){ // 向左走return true;}else {// 都没走通 这个点是个思死路map[i][j] = 3;return false;}}else { // 肯能为 1是墙 2走过 3不能走return false;}}}}
5、八皇后问题
八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在 8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即:任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法(92)。
思路
- 将第一个皇后放在第一行第一列
- 将第二个皇后放在第二行第一列,判断是否会和其他皇后相互攻击,若会相互攻击,则将其放到第三列、第四列…直到不会相互攻击为止
- 将第三个皇后放在第三行第一列,判断是否会和其他皇后相互攻击,若会相互攻击,则将其放到第三列、第四列…知道不会相互攻击为止,并以此类推,在摆放的过程中,有可能会改动前面所放的皇后的位置
- 当得到一个正确的解时,就会回溯到上一行,由此来找出第一个皇后在第一行第一列的所有解
- 再将第一个皇后放到第一行第二列,并重复以上四个步骤
- 注意:
- 棋盘本身应该是用二维数组表示,但是因为皇后所在的行数是固定的,所以可以简化为用一个一维数组来表示。其中的值代表皇后所在的列
- 数组下标代表皇后所在行数,所以判断是否在同一行列斜线上时,只需要判断是否在同一列和同一斜线上即可
- 是否同列判断:值是否相同
- 是否同一斜线:行号-行号是否等于列号-列号,且列号相减要取绝对值


代码
package com.atguigu.recursion;public class Queue8 {//定义一个max表示共有多少个皇后int max = 8;//定义数组array, 保存皇后放置位置的结果,比如 arr = {0 , 4, 7, 5, 2, 6, 1, 3}int[] array = new int[max];static int count = 0;static int judgeCount = 0;public static void main(String[] args) {//测试一把 , 8皇后是否正确Queue8 queue8 = new Queue8();queue8.check(0);System.out.printf("一共有%d解法", count);System.out.printf("一共判断冲突的次数%d次", judgeCount); // 1.5w}//编写一个方法,放置第n个皇后//特别注意: check 是 每一次递归时,进入到check中都有 for(int i = 0; i < max; i++),因此会有回溯private void check(int n) {if(n == max) { //n = 8 , 其实8个皇后就既然放好print();return;}//依次放入皇后,并判断是否冲突for(int i = 0; i < max; i++) {//先把当前这个皇后 n , 放到该行的第1列array[n] = i;//判断当放置第n个皇后到i列时,是否冲突if(judge(n)) { // 不冲突//接着放n+1个皇后,即开始递归check(n+1); //}//如果冲突,就继续执行 array[n] = i; 即将第n个皇后,放置在本行得 后移的一个位置}}//查看当我们放置第n个皇后, 就去检测该皇后是否和前面已经摆放的皇后冲突/**** @param n 表示第n个皇后* @return*/private boolean judge(int n) {judgeCount++;for(int i = 0; i < n; i++) {// 说明//1. array[i] == array[n] 表示判断 第n个皇后是否和前面的n-1个皇后在同一列//2. Math.abs(n-i) == Math.abs(array[n] - array[i]) 表示判断第n个皇后是否和第i皇后是否在同一斜线// n = 1 放置第 2列 1 n = 1 array[1] = 1// Math.abs(1-0) == 1 Math.abs(array[n] - array[i]) = Math.abs(1-0) = 1//3. 判断是否在同一行, 没有必要,n 每次都在递增if(array[i] == array[n] || Math.abs(n-i) == Math.abs(array[n] - array[i]) ) {return false;}}return true;}//写一个方法,可以将皇后摆放的位置输出private void print() {count++;for (int i = 0; i < array.length; i++) {System.out.print(array[i] + " ");}System.out.println();}}
一共有92种放法,就不一一展示了
