递归调用简单的说就是方法自己调用自己,每次调用时传入不同的变量。解决复杂问题的同时能让代码变得简洁。
先来举两个小例子来理解一下递归调用的机制:
1.打印问题
public class Recursion01{
//编写一个main方法
public static void main(String[] args) {
T t1 = new T();
t1.test(4); //输出什么?
}
}
class T{
public void test(int n){
if(n > 2){
test(n - 1 );
}
System.out.println("n = "+ n);
}
}
这段代码的目的是:当我们给方法传入一个值时,去进行一个判断,如果大于2,就继续调用方法,直到n不大于2时输出结果。那么想一下这段简单的代码输出的是什么?
结果为什么是这样呢?按照代码来看它应该只输出“n = 2”就可以了啊。 我们来看一下他的调用机制:
按照语句执行,会在堆中创建一个t1对象,接下里调用test方法,我们知道当方法被调用时就会产生一个新栈,我们在方法里传入的参数为4,直到n=2时停止调用test方法,因此会产生三个新栈(如上图所示)
方法调用停止之后,要执行输出语句,要把每个栈的结果都要输出。栈是一种要满足先进后出原则的存储结构,因此要先输出最上层的栈帧,然后逐层返回结果。输出结果即为图2所示。
我们改写一下代码,如下图:
这时的输出结果为:
这是因为输出语句写在了else的分支语句中,只有不满足n>2这一条件时才会进行输出当前栈帧的结果。
2.阶乘问题
不知道大家是否已经明白了递归调用的机制,我们再来看一个经典的递归调用的案例---阶乘。
来看以下代码:
输出结果为120,这不用多说,我们直接来看它的调用机制,
我们传入的参数为5,根据if条件可知factorial方法会被调用5次,产生5个新栈帧,直至n为1时,返回f(1)的值为1,依次返回上一层栈帧的结果。最终得到5得阶乘。
3.递归调用的重要规则
- 执行一个方法时,就创建一个新的受保护得独立空间(栈空间)
- 方法得局部变量是独立的,不会相互影响,比如n变量
- 如果方法中使用的是引用数据类型变量(比如数组,对象),就会共享该引用类型的数据
- 递归必须向退出递归的条件逼近,否则就会无限递归,出现栈溢出(StackOverflowError)
- 当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕
4.斐波那契数列
斐波那契数列也是递归调用的经典案例,不过不是很难,这里简单介绍一下,斐波那契数是其前两个数的和。代码放在这里,大家自行思考其调用机制:
5.递归调用应用实例---迷宫问题
我们先把迷宫的形状贴在这里
迷宫为8行7列的表格型,我们可以用二维数组来实现迷宫的创建
public class MiGong{
//编写main方法
public static void main(String[] args) {
//思路
//1.先创建二位数组表示 int[] []map = new int[8][7]
//2.先规定map数组的元素值:0表示可以走 1表示障碍物
int [] [] map = new int [8][7];
//将最上面的一行和最下面的一行,全部设置为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;
}
//将[3][1];[3][2]位置设为障碍物
map[3][1] = 1;
map[3][2] = 1;
//输出当前地图情况
System.out.println("===当前地图情况===");
for (int i = 0;i < map.length ;i++ ) {
for (int j = 0;j <map[i].length ;j++ ) {
System.out.print(map[i][j]+" ");
}
System.out.println();
}
}
}
输出结果为:
下面就是给小球制定找路策略:
//创建一个类,使用递归回溯的思想来解决迷宫问题
class T {
//1.创建findway方法找出迷宫路径
//2.如果找到就返回true,否则返回false
//3.i,j就是小球的位置,初始位置为(1,1)
//4.因为要用递归的思想找路,所以要给map数组的各个位置赋予不同意义的不同值
// 0表示可以走;1表示障碍物;2表示可以走;3表示走过,但是走不通
//5.当map[6][5]=2,就说明找到通路,可以走,否则就继续找
//6.确定小球的找路策略为:下->右->上->左
public 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,j-1)){
return true;
}else if(findWay(map,i-1,j)){
return true;
}else{
map[i][j] = 3;
return false;
}
}else{
return false;
}
}
}
}
调用fingWay方法:
T t1 = new T();
t1.findWay(map,1,1);
System.out.println("\n===找路的情况如下===");
for (int i = 0;i < map.length ;i++ ) {
for (int j = 0;j < map[i].length ;j++ ) {
System.out.print(map[i][j]+" ");
}
System.out.println();
}
输出结果如下
大家可以尝试写一下迷宫问题,认真考虑一下递归调用的机制。我把完整代码放在下面:
public class MiGong{
//编写main方法
public static void main(String[] args) {
//思路
//1.先创建二位数组表示 int[] []map = new int[8][7]
//2.先规定map数组的元素值:0表示可以走 1表示障碍物
int [] [] map = new int [8][7];
//将最上面的一行和最下面的一行,全部设置为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;
}
//将[3][1];[3][2]位置设为障碍物
map[3][1] = 1;
map[3][2] = 1;
//输出当前地图情况
System.out.println("===当前地图情况===");
for (int i = 0;i < map.length ;i++ ) {
for (int j = 0;j <map[i].length ;j++ ) {
System.out.print(map[i][j]+" ");
}
System.out.println();
}
T t1 = new T();
t1.findWay(map,1,1);
System.out.println("\n===找路的情况如下===");
for (int i = 0;i < map.length ;i++ ) {
for (int j = 0;j < map[i].length ;j++ ) {
System.out.print(map[i][j]+" ");
}
System.out.println();
}
}
}
//创建一个类,使用递归回溯的思想来解决迷宫问题
class T {
//1.创建findway方法找出迷宫路径
//2.如果找到就返回true,否则返回false
//3.i,j就是小球的位置,初始位置为(1,1)
//4.因为要用递归的思想找路,所以要给map数组的各个位置赋予不同意义的不同值
// 0表示可以走;1表示障碍物;2表示可以走;3表示走过,但是走不通
//5.当map[6][5]=2,就说明找到通路,可以走,否则就继续找
//6.确定小球的找路策略为:下->右->上->左
public 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,j-1)){
return true;
}else if(findWay(map,i-1,j)){
return true;
}else{
map[i][j] = 3;
return false;
}
}else{
return false;
}
}
}
}
以上就是递归调用的机制。可以多画几次内存图来理解透彻。