乐趣区

递归就是这么简单

什么是递归?

维基百科给出了如下定义:

程序调用自身的编程技巧称为递归. 递归作为一种算法在程序设计语言中广泛应用。

上面的说法略显官方。简而言之,递归就是自己调用自己,但是这个调用它是有一定条件的,比如:

  • 子问题须与原始问题为同样的事,且更为简单。
  • 调用自身的次数不能太多,否则会造成程序堆栈溢出。
  • 必须设置递归边界,也就是递归的结束条件,否则递归会无限循环直到程序堆栈溢出。

递归与循环的区别

  • 递归

优点:代码简洁、清晰(需要你理解算法,否则会更晕)
缺点:调用次数控制不好,容易造成堆栈溢出,此外,它的每次传递参数都是相当于在压栈,每次返回结果都相当于出栈,这个过程是非常影响执行效率的。

  • 循环

优点:逻辑简单,速度快
缺点:不能解决所有的问题,有些问题必须用递归实现。比如,著名的汉若塔问题,如果有谁可以用其他方式写出来我服。

递归的使用场景

关于使用场景,我总结了一句话:调用次数较少且用循环实现极为恶心的时候,可以尝试使用递归。

关于递归的几个实例

  1. 计算 int 形数组的总和
public class Main {private static int sum(int[] arr, int z) {if (z == arr.length) {return 0;}

        int x = sum(arr, z + 1);

        int res = arr[z] + x;

        return res;
    }


    public static void main(String[] args) {int arr[] = {1, 2};
        sum(arr, 0);
    }
}

这个示例最简单,当然这里是为了方便说明问题,实际上用循环实现才是最好的。目的就是计算 arr 数组各元素的总和,看输入参数,大家可以猜到返回结果是 3。下面我说下看这类程序的小技巧。

首先,我们要找到程序的递归边界,也就是递归结束的条件(这样说也不准确,看具体的代码实现,有时递归边界确实是递归结束的条件,返回最终结果,但有时又是递归最后一层返回结果的条件,比如以下程序)。

  • 当 z = 2 时,这层递归返回 0,也就是 x = 0、返回到 z = 1 的这层递归。
  • 此时,res = arr[1] + 0,返回 res = 2 也就是 x = 2 给到 z = 0 的这层递归。
  • 这时,res = arr[0] + 2 = 3 至此,递归结束,返回结果给调用方。

没看懂的,请复制代码 debug 一步一步的运行。一开始看反正我是被绕晕的。

  1. 计算 1 到 100 的总和
public class Main {

    static int i = 1;

    public static void show(int sum) {
        sum = sum + i; // 业务代码 1
        // 递归头
        if (i == 10) {System.out.println(sum);
            return;
        }
        i++;   // 业务代码 2
        show(sum); // 递归体
    }

    public static void main(String[] args) {
        int sum = 0;
        show(sum);
    }
}

以上写法的递归边界,就属于我上面说的,它就是递归结束的条件。它的返回结果就是递归的最终结果,而不是上一层的结果。

  1. 斐波那契数列
public class Main {public static int f(int n) throws Exception {if(n==0){throw new Exception("参数错误!");
        }
        if (n == 1 || n == 2) {return 1;} else {return f(n-1)+f(n-2); // 自己调用自己
        }
 }
 
 
    public static void main(String[] args) throws Exception {for (int i = 1; i <=10; i++) {System.out.print(f(i)+" ");
        }
    }  
}
  1. 计算文件夹大小

由于 File 类下 length() (返回值类型为 long 型) 方法只能统计文件的大小,没有方法直接统计文件夹的大小,需要使用递归的方法遍历到所有的文件,并累加,最终计算出文件夹大小。

public class Main {public static void main(String[] args) {File dir = getDir();
        System.out.println(getFileLength(dir));
        System.out.println("统计完成!");
    }
    
    public static File getDir() {
        //1, 创建键盘录入对象
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入一个文件夹路径:");
        //2, 定义一个无限循环
        while(true) {
            //3, 将键盘录入的结果存储并封装成 File 对象
            String line = sc.nextLine();
            File dir = new File(line);
            //4, 对 File 对象判断
            if(!dir.exists()) {System.out.println("您录入的文件夹路径不存在, 请重新录入:");
            }else if(dir.isFile()) {System.out.println("您录入的是文件路径, 请重新录入:");
            }else {
                //5, 将文件夹路径对象返回
                return dir;
            }
        }        
    }

    public static long getFileLength(File dir) {
        //1, 定义一个求和变量
        long len = 0;
        //2, 获取该文件夹下所有的文件和文件夹 listFiles();
        File[] subFiles = dir.listFiles();
        //3, 遍历数组
        if(subFiles != null) {for (File subFile : subFiles) {
                //4, 判断是文件就计算大小并累加
                if(subFile.isFile()) {len = len + subFile.length();
                    //5, 判断是文件夹, 递归调用
                }else {len = len + getFileLength(subFile);
                }
            }
        }
        return len;
    }
}

总结:这篇主要是介绍下递归的定义、与循环的区别以及它的使用场景,最后提供了几个代码示例给大家研究,看不懂的请复制代码,debug 一步一步运行理解。

参考链接:https://www.jianshu.com/p/edfc4e35f383

推荐阅读:

1、java | 什么是动态代理

2、SpringBoot | 启动原理

3、SpringBoot | 自动配置原理

退出移动版