摘要:简而言之,递归就是自己调用自己,但是这个调用它是有一定条件的,比如子问题须与原始问题为同样的事,且更为简单。当时,这层递归返回,也就是返回到的这层递归。这时,至此,递归结束,返回结果给调用方。
什么是递归?
维基百科给出了如下定义:
程序调用自身的编程技巧称为递归.递归作为一种算法在程序设计语言中广泛应用。
上面的说法略显官方。简而言之,递归就是自己调用自己,但是这个调用它是有一定条件的,比如:
子问题须与原始问题为同样的事,且更为简单。
调用自身的次数不能太多,否则会造成程序堆栈溢出。
必须设置递归边界,也就是递归的结束条件,否则递归会无限循环直到程序堆栈溢出。
递归与循环的区别递归
优点:代码简洁、清晰(需要你理解算法,否则会更晕)
缺点:调用次数控制不好,容易造成堆栈溢出,此外,它的每次传递参数都是相当于在压栈,每次返回结果都相当于出栈,这个过程是非常影响执行效率的。
循环
优点:逻辑简单,速度快
缺点:不能解决所有的问题,有些问题必须用递归实现。比如,著名的汉若塔问题,如果有谁可以用其他方式写出来我服。
关于使用场景,我总结了一句话:调用次数较少且用循环实现极为恶心的时候,可以尝试使用递归。
关于递归的几个实例计算 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 到 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); } }
以上写法的递归边界,就属于我上面说的,它就是递归结束的条件。它的返回结果就是递归的最终结果,而不是上一层的结果。
斐波那契数列
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)+" "); } } }
计算文件夹大小
由于 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 | 自动配置原理
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/75870.html
摘要:那么,有了循环,为什么还要用递归呢在某些情况下费波纳切数列,汉诺塔,使用递归会比循环简单很多很多话说多了也无益,让我们来感受一下递归吧。 递归介绍 本来预算此章节是继续写快速排序的,然而编写快速排序往往是递归来写的,并且递归可能不是那么好理解,于是就有了这篇文章。 在上面提到了递归这么一个词,递归在程序语言中简单的理解是:方法自己调用自己 递归其实和循环是非常像的,循环都可以改写成递归...
摘要:如果存在这么个函数,那么我们就可以通过求解的不动点来求出了。寻找转换函数的不动点找到了转换函数后,下一步就是确定其不动点了,而这个不动点就是我们最终想要的。 本文原发于个人博客 递归 作为计算机科学中很重要的一个概念,应用范围非常广泛。比较重要的数据结构,像树、图,本身就是递归定义的。比较常见的递归算法有阶乘、斐波那契数等,它们都是在定义函数的同时又引用本身,对于初学者来说也比较好理解...
摘要:今天我们继续使用的撸我们的实战项目,只有在实战中我们才会领悟更多,光纸上谈兵然并卵,继上篇我们的一个案例引发的动态组件与全局事件绑定总结之后,今天来聊一聊我们如何在项目中使用递归组件。 今天我们继续使用 Vue 的撸我们的实战项目,只有在实战中我们才会领悟更多,光纸上谈兵然并卵,继上篇我们的《Vue一个案例引发的动态组件与全局事件绑定总结》 之后,今天来聊一聊我们如何在项目中使用递归组...
摘要:不断执行这个操作代码实现快速排序用递归比较好写如果不太熟悉递归的同学可到递归就这么简单。 前言 大概花了一周的时间把八大基础排序过了一遍,这篇博文主要是用来回顾一下八大基础排序的要点和一些总结~ 回顾: 冒泡排序就这么简单 选择排序就这么简单 插入排序就这么简单 快速排序就这么简单 归并排序就这么简单 堆排序就这么简单 希尔排序就这么简单 基数排序就这么简单 总的来说:快速排序是用...
阅读 2576·2023-04-25 22:09
阅读 2790·2021-10-14 09:47
阅读 1827·2021-10-11 11:10
阅读 2657·2021-10-09 09:44
阅读 3331·2021-09-22 14:57
阅读 2474·2019-08-30 15:56
阅读 1587·2019-08-30 15:55
阅读 753·2019-08-30 14:13