# 轻轻松松学递归

### 递归调用机制

public class RecursionTest {

public static void main(String[] args) {
test(5);
}

public static void test(int num) {
if (num > 2) {
test(num - 1);
}
System.out.println("num = " + num);
}
}

n = 2
n = 3
n = 4
n = 5

### 递归规则

You have several identical balls that you wish to place in several baskets. Each basket has the same maximum capacity. You are given an int baskets, the number of baskets you have. You are given an int capacity, the maximum capacity of each basket. Finally you are given an int balls, the number of balls to sort into baskets. Return the number of ways you can divide the balls into baskets. If this cannot be done without exceeding the capacity of the baskets, return 0.
Each basket is distinct, but all balls are identical. Thus, if you have two balls to place into two baskets, you could have (0, 2), (1, 1), or (2, 0), so there would be three ways to do this.

1. 执行一个方法时，就会在栈中创建一个新的受保护的独立空间
2. 方法的局部变量是独立的，互不影响的
3. 如果方法中使用的是引用类型变量(比如数组)，就会共享该引用类型的数据
4. 递归必须向退出退出递归的条件逼近，否则就是无限递归
5. 当一个方法执行完毕，或者遇到return时，就会返回，遵守谁调用，就将结果返回谁的规则。同时，当方法执行完毕或返回时，该方法也就执行完毕

### 迷宫回溯问题

public class MazeBack {

public static void main(String[] args) {
// 创建一个二维数组，模拟迷宫
int[][] map = new int[8][7];
// 约定1为墙壁
// 上下置为1
for (int i = 0; i < 7; i++) {
map[0][i] = 1;
map[7][i] = 1;
}
// 左右置为1
for (int i = 0; i < 8; i++) {
map[i][0] = 1;
map[i][6] = 1;
}
map[3][1] = 1;
map[3][2] = 1;

System.out.println("原始地图：");

for (int[] temp : map) {
for (Integer i : temp) {
System.out.print(i + " ");
}
System.out.println();
}

//递归寻找路径
findWay(map, 1, 1);

System.out.println("小球行走路径：");

for (int[] temp : map) {
for (Integer i : temp) {
System.out.print(i + " ");
}
System.out.println();
}
}

/**
* 使用递归求出迷宫路径 如果小球能够到map[6][5]，则证明找到正确路径
* 我们约定当map[i][j]为0表示墙，为1表示当前位置没有走过，为2表示当前位置可以走通，为3表示当前位置已经走过，但走不通
*
* @param map 地图
* @param i   出发点
* @param j   出发点
* @return 如果找到正确路径，返回true，否则返回false
*/
public static boolean findWay(int[][] map, int i, int j) {
if (map[6][5] == 2) {
// 此时说明路径已经找到
return true;
} else {
if (map[i][j] == 0) {
// 此时说明当前位置还没有走过
// 按照策略走
map[i][j] = 2;// 假设这个位置是可以走通的
if (findWay(map, i + 1, j)) {// 在该点的基础上向下走
return true;
} else if (findWay(map, i, j + 1)) {// 在该点的基础上向右走
return true;
} else if (findWay(map, i - 1, j)) {// 在该点的基础上向上走
return true;
} else if (findWay(map, i, j - 1)) {// 在该点的基础上向左走
return true;
} else {
// 此时说明map[i][j]是走不通的
map[i][j] = 3;// 将map[i][j]置为3
return false;
}
} else {
// 如果map[i][j]不为0，它还有可能为1、2和3
return false;
}
}
}
}

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

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

map[1][2] = 1;
map[2][2] = 1;

1 1 1 1 1 1 1
1 0 1 0 0 0 1
1 0 1 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

1 1 1 1 1 1 1
1 3 1 0 0 0 1
1 3 1 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

public static boolean findWay(int[][] map, int i, int j) {
if (map[6][5] == 2) {
return true;
} else {
if (map[i][j] == 0) {
map[i][j] = 2;
if (findWay(map, i - 1, j)) {// 在该点的基础上向上走
return true;
} else if (findWay(map, i, j + 1)) {// 在该点的基础上向右走
return true;
} else if (findWay(map, i + 1, j)) {// 在该点的基础上向下走
return true;
} else if (findWay(map, i, j - 1)) {// 在该点的基础上向左走
return true;
} else {
map[i][j] = 3;// 将map[i][j]置为3
return false;
}
} else {
return false;
}
}
}

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

1 1 1 1 1 1 1
1 2 2 2 2 2 1
1 0 0 0 0 2 1
1 1 1 0 0 2 1
1 0 0 0 0 2 1
1 0 0 0 0 2 1
1 0 0 0 0 2 1
1 1 1 1 1 1 1

### 八皇后问题

1. 第一个皇后先放第一行或第一列
2. 第二个皇后放在第二行第一列，然后能否放置，如果不能，就放在第二列，继续判断，若不能放置，则放在第三列，以此类推，直至找到合适的位置
3. 第三个皇后也按第二步骤不断寻找，第四个皇后也如此，直至第八个皇后也找到了一个与其它皇后不冲突的地方，此时找到了一个正确解
4. 当得到一个正确解之后，在栈回退到上一个栈时，就会开始回溯，即：将第一个皇后，放到第一列的所有正确解全部得到
5. 然后第一个皇后放到第二列，后面重复执行1、2、3、4步骤

public class EightQueue {

//定义一个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) {
EightQueue queue8 = new EightQueue();
queue8.check(0);
System.out.printf("一共有%d解法", count);
System.out.printf("一共判断冲突的次数%d次", judgeCount); //

}

//编写一个方法，放置第n个皇后
//特别注意： check 是 每一次递归时，进入到check中都有  for(int i = 0; i < max; i++)，因此会有回溯
private void check(int n) {
if(n == max) {  //n = 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();
}

}

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

### 递归的缺点

1. 递归由于是函数调用自身，而函数调用是有时间和空间的消耗的：每一次函数调用，都需要在栈中分配空间

2. 递归中有很多重复进行的计算，由于其本质是把一个问题分解成两个或者多个小问题，多个小问题存在相互重叠的部分

3. 递归经常会出现栈溢出问题，因为每一次方法的调用都会在栈中分配空间，而每个进程的栈的容量是有限的，当调用的层次太多时，就会超出栈的容量，从而导致栈溢出