递归
在定义一个函数时,出现直接或间接调用自己的成分,称之为递归。
递归算法解决问题的特点:
1 .递归就是方法里调用自身。
2. 在使用递增归策略时,必须有一个明确的递归结束条件,称为递归出口。
3. 递归有助于编程者解决复杂的问题(如迷宫问题,汉诺塔等),同时可以让代码变得简洁
递归需要遵守的重要规则
- 执行一个方法时,就是创建一个新的受保护的独立空间(栈空间)
- 方法的局部变量是独立的,不会相互影响,比如N变量
- 如果方法中使用的是引用类型变量(如数组),就会共享该应用类型的数据
- 递归必须向退出递归的条件逼近,不然就回出现死递归,出现StackOverflowError。
- 当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就是执行完毕。
迷宫问题
求迷宫问题就是求出从入口到出口的路径,在求解时,通常用的是“穷举求解”的方法,即从入口出发,顺着某一个方向向前试探,若能呢走通,则继续往前走,否则沿着原路退回,换一个方向在继续试探,直到所有可能的通的路都试探完为止。为了保证在任何位置上都能沿着原路退回(回溯),需要用一个后进先出的栈来保存从入口到当前位置的路径。
如图:
对应图中的每个方块,用空白表示 通道,用阴影表示墙。
下面我们用递归来实现迷宫问题:
为了表示迷宫,我们可以使用一个二维数组来表示迷宫,图中阴影部分表示墙,我们可以把他们初始化为1表示墙
空白位置为0,表示走得通。
- 定义一个方法返回迷宫:
- 定义方法走迷宫
- 传入保存路径的栈和初始路径开始走迷宫
public class Labyrinth {
public static void main(String[] args) {
int[][] newlabyrinth = newlabyrinth();
// 栈用来保存走过路的即保存数组的下标,i,j
Stack<Integer> stack = new Stack();
goLabyrinth(newlabyrinth, stack,1, 1);
// 遍历二维数组,看路径
// 输出遍历看看效果
for (int[] ints : newlabyrinth) {
for (int anInt : ints) {
System.out.print(anInt+ " ");
}
System.out.println();
}
}
/**
* 初始化迷宫
* 初始化为1表示墙
* 空白位置为0
* @return
*/
public static int[][] newlabyrinth() {
int[][] labyrinth = new int[10][10];
for (int i = 0; i < labyrinth.length; i++) {
for (int j = 0; j < labyrinth[i].length; j++) {
// 第一行,和最后一行是墙
labyrinth[0][j] = 1;
labyrinth[9][j] = 1;
// 第一列和最后一列是墙
labyrinth[i][0] = 1;
labyrinth[i][9] = 1;
// 图中其余位置的阴影位置
labyrinth[1][3] = 1;
labyrinth[1][7] = 1;
labyrinth[2][3] = 1;
labyrinth[2][7] = 1;
labyrinth[3][5] = 1;
labyrinth[3][6] = 1;
labyrinth[4][2] = 1;
labyrinth[4][3] = 1;
labyrinth[4][4] = 1;
labyrinth[5][4] = 1;
labyrinth[6][2] = 1;
labyrinth[6][6] = 1;
labyrinth[7][2] = 1;
labyrinth[7][3] = 1;
labyrinth[7][4] = 1;
labyrinth[7][6] = 1;
labyrinth[7][7] = 1;
labyrinth[8][1] = 1;
}
}
// // 输出遍历看看效果
// for (int[] ints : labyrinth) {
// for (int anInt : ints) {
// System.out.print(anInt+ " ");
// }
// System.out.println();
// }
return labyrinth;
}
/**
* 1.我们从入口labyrinth[1][1]开始进入,找到出口位置labyrinth[8][8]
* 2.根据规则,我们只能走空白位置,遇到墙,换个方向。
* 3.我们定义一个走路的规则,先往下,在往右,上,左
* 4.对于走过的路我们可以初始化为2
*
* i,j表示位置当前所处在的位置
*
*/
public static boolean goLabyrinth(int[][] labyrinth, Stack<Integer> stack, int i, int j) {
if (i < 1 || j < 1) {
throw new RuntimeException("初始位置不对");
}
// 出口是 labyrinth[8][8]
if (i ==8 && j == 8) {
labyrinth[8][8] = 2;
return true;
}
if (labyrinth[i][j] == 0) {
// 走的通的路设置为2
labyrinth[i][j] = 2;
// 保存走得通的路径,j保存在i的下面,取得时候先弹出的是i
stack.push(j);
stack.push(i);
// 先往下,在往右,上,左
if (labyrinth[i+1][j] == 0) {
goLabyrinth(labyrinth,stack,i +1,j);
} else if (labyrinth[i][j+1] == 0) {
goLabyrinth(labyrinth,stack,i,j+1);
} else if (labyrinth[i-1][j] == 0) {
goLabyrinth(labyrinth,stack,i-1,j);
} else if (labyrinth[i][j-1] == 0) {
goLabyrinth(labyrinth,stack,i,j-1);
} else {
// 走到这说明都走不通是死路,我们设置死路为3,需要原路退回,即回溯
labyrinth[i][j] = 3;
// 弹出刚才保存在栈的此路坐标,此路不同不保存
stack.pop();
stack.pop();
if (stack.empty()) {
return false;
}
// 退回到上一格;取出栈顶的元素 i 和 j
Integer popI = stack.pop();
Integer popJ = stack.pop();
// 把上一格设置为没有走过
labyrinth[popI][popJ] = 0;
goLabyrinth(labyrinth,stack, popI, popJ);
}
}
return false;
}
输出结果迷宫图:
初始化的迷宫:
1 1 1 1 1 1 1 1 1 1
1 0 0 1 0 0 0 1 0 1
1 0 0 1 0 0 0 1 0 1
1 0 0 0 0 1 1 0 0 1
1 0 1 1 1 0 0 0 0 1
1 0 0 0 1 0 0 0 0 1
1 0 1 0 0 0 1 0 0 1
1 0 1 1 1 0 1 1 0 1
1 1 0 0 0 0 0 0 0 1
1 1 1 1 1 1 1 1 1 1
开始走迷宫:
1 1 1 1 1 1 1 1 1 1
1 2 0 1 0 0 0 1 0 1
1 2 0 1 0 0 0 1 0 1
1 2 0 0 0 1 1 0 0 1
1 2 1 1 1 0 0 0 0 1
1 2 2 2 1 0 0 0 0 1
1 3 1 2 2 2 1 0 0 1
1 3 1 1 1 2 1 1 0 1
1 1 0 0 0 2 2 2 2 1
1 1 1 1 1 1 1 1 1 1
从输出结果我们可以看出,2是走迷宫的路径,其实我们可以有很多种走法,需要我们自己定义规则,每个规则的走法都有可能不一样。
我们来想一下如何求出最短路劲问题:
其实求出最短路径也简单,上面的代码我们已经把走过的路径保存在栈中了,我们只需要把走的规则给穷举一遍,保存每一种规则走的路径长度,然后比较哪一种最短即可。大家有兴趣可以自己完成
- 心得总结:
其实像这种数据结构和算法题需要自己手动去写一遍,最好按照自己的思路把代码敲出了,光看不如自己动手,总结技巧,慢慢自己就会熟能生巧。所以需要自己去想思路,尽量自己亲手动手写一遍,然后在回头想想自己写的代码,往往有很不错的效果。