1つ、再帰
1.コンセプト
特定のメソッドはメソッド自体を呼び出します。これは再帰(recursion
)と呼ばれます。
再帰の数に注意する必要があります。回数が多すぎると、スタックオーバーフローが発生する可能性があります(java.lang.StackOverflowError
)
再帰を使用し、メソッドを変更してパラメーターを入力し、目的の結果を達成しようとし続けます。このプロセスはバックトラッキングと呼ばれます。
この方法の利点は単純ですが、欠点は、バックトラッキングの実行が多すぎる可能性があることです。
2.アプリケーション
[1]迷路の問題
平面(2次元配列)では、周囲に壁があり、通り抜けることができません。特定の位置に目標値があります。次に、指定した場所から検索を開始し、ターゲットの場所を見つける方法を尋ねる必要があります
分析:
壁を通り抜けることはできず、すでに通り過ぎた場所に行くこともできません。目的の場所を見つけるには、任意の時点で進むべき4つの方向があります。したがって、バックトラック思考を使用して上、下、左、右
、目標位置に到達するまで常に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;
}
}