一、递归
1、概念
某一个方法调用方法本身,就称之为递归(recursion
)
需要注意递归的次数,如果次数过多,可能会导致堆栈溢出( java.lang.StackOverflowError
)
使用递归,通过修改方法入参,不断尝试以达到预期结果,该过程被称之为回溯
这种方式优点是简单,缺点是可能回溯的执行次数过多
2、应用
【1】迷宫问题
在一个平面中(二维数组),四周都是墙体,不可以经过。有一个目标值,在某个位置。现需要从指定的位置,开始查找,问怎么查找可以到达目标位置
分析:
墙体不可以经过,已经经过的地方也不可以再走。要找到目标位置,在任意一点,都有4个方向可以走。所以可以使用回溯的思想,不断地重试 上、下、左、右
四个方向,直到到达目标位置
示例:
class Maze {
private final int width;
private final int length;
private final int[][] map;
/** 初始化坐标位置 */
private final int INIT_X = 2;
private final int INIT_Y = 2;
/** 墙体 */
private final int WALL = 1;
/** 还未通过的地方 */
private final int PATH = 0;
/** 已经通过的地方 */
private final int PASS = 2;
/** 目标 */
private final int TARGET = 9;
/** 查找次数 */
private int count = 0;
public Maze(int width, int length) {
if (width < 5 || length < 5) {
// 宽高至少为5
throw new IllegalArgumentException("非法宽高!");
}
this.width = width;
this.length = length;
this.map = new int[length][width];
this.init();
}
private void init() {
// 初始化墙体
for (int i = 1, len = width - 1; i < len; i++) {
map[0][i] = WALL;
map[length - 1][i] = WALL;
}
for (int j = 0; j < length; j++) {
map[j][0] = WALL;
map[j][width - 1] = WALL;
}
for (int k = 1; k <= INIT_Y; k++) {
map[INIT_X + 1][k] = WALL;
}
// 初始化目标
map[length - 2][width - 2] = TARGET;
}
public void find() {
find(INIT_X, INIT_Y);
}
private boolean find(int x, int y) {
this.count++;
if (TARGET == map[x][y]) {
this.show();
System.out.println("\n共查找了" + count + "次");
return true;
}
if (PATH == map[x][y]) {
map[x][y] = PASS;
if (upFind(x, y)) {
return true;
} else if (rightFind(x, y)) {
return true;
} else if (downFind(x, y)) {
return true;
} else if (leftFind(x, y)) {
return true;
}
}
return false;
}
private boolean upFind(int x, int y) {
return find(x - 1, y);
}
private boolean rightFind(int x, int y) {
return find(x, y + 1);
}
private boolean downFind(int x, int y) {
return find(x + 1, y);
}
private boolean leftFind(int x, int y) {
return find(x, y - 1);
}
private void show() {
for (int i = 0; i < length; i++) {
for (int j = 0; j < width; j++) {
System.out.print(map[i][j] + " ");
}
System.out.println();
}
System.out.println();
}
}
【2】八皇后问题
在国际象棋棋盘上(8 * 8),摆放8个皇后,需要让每个皇后都互相不能攻击(水平、垂直 和 斜线方向不能同时存在2个皇后)。问一共有多少种摆法
分析:可以使用回溯的方法。用一个长度为8的数组表示8个皇后的水平(或垂直)位置,先通过循环数组的方式(下标即皇后摆放位置的 水平 或 垂直位置),得到当前皇后的可以摆放的位置,再通过不断地递归,得到下一个皇后的摆放位置,最终得到所有的摆法
示例:
class EightQueens {
private static final int EIGHT = 8;
private static int count = 0;
private static int sum = 0;
public static void pattern() {
pattern(EIGHT);
}
public static void pattern(int length) {
int[] array = new int[length];
pattern(array, length, 0);
System.out.println("一共有" + sum + "种摆法");
System.out.println("一共校验了" + count + "次");
}
private static void pattern(int[] array, int length, int index) {
if (length == index) {
for (int i = 0; i < length; i++) {
System.out.print(array[i] + " ");
}
System.out.println();
sum++;
return;
}
for (int i = 0; i < length; i++) {
array[index] = i;
if (check(array, index)) {
// 循环判断当前索引位置是否可以摆放皇后
pattern(array, length, index + 1); // 继续摆放下一个皇后
}
}
}
private static boolean check(int[] array, int index) {
count++;
for (int i = 0; i < index; i++) {
// 校验水平或垂直位置是否有皇后存在,以及校验斜线方向是否存在皇后
if (array[i] == array[index] || (index - i) == Math.abs(array[index] - array[i])) {
return false;
}
}
return true;
}
}