全屏产品网站,阿里巴巴网站怎样做的漂亮,app制作教程视频全集,深圳网站免费制作个人主页 目录前言#x1f4a1;1.什么是递归#xff1f;1.1 递归的两个关键要素1.2 递归结构#xff1a;2.经典的递归2.1 案例一#xff1a;阶乘计算2.2 案例二#xff1a;斐波那契数列2.3 目录遍历3.深入理解递归为什么会栈溢出3.1 什么是栈#xff1f;Java 虚拟机栈结合…个人主页目录前言1.什么是递归1.1 递归的两个关键要素1.2 递归结构2.经典的递归2.1 案例一阶乘计算2.2 案例二斐波那契数列2.3 目录遍历3.深入理解递归为什么会栈溢出3.1 什么是栈Java 虚拟机栈结合之前的阶乘例子看栈的变化3.2 什么是栈溢出为什么会发生栈溢出3.3 如何避免栈溢出4.结尾前言想象一下你站在一面大镜子前手里还拿着一个小镜子。你就会发现大镜子里有小镜子小镜子里又有大镜子无限循环…这就是递归的直观感受递归的本质就是一个方法调用自身来解决问题本章将快速帮助你掌握Java方法中的递归是什么该怎么用1.什么是递归递归是指一个方法在其定义中直接或间接地调用自身。就是“自己调用自己”。递归的核心思想是将一个复杂的大问题分解成一个或者多个与原问题相似但规模更小的子问题来解决。就像套娃一样一个大娃娃里面装着一个一摸一样的小娃娃小娃娃里面又装着更小的娃娃……直到最小的那个娃娃不能再打开为止。1.1 递归的两个关键要素基线要素Base Case停止递归的条件相当于套娃里最小的那个娃娃不能再打开了递归条件Recursive Case继续调用自身的条件还没找到最小娃娃继续打开下一个1.2 递归结构递归结构包括两个结构递归头什么时候不调用自身方法。如果没有头将陷入死循环。递归体什么时候需要调用自身方法。2.经典的递归2.1 案例一阶乘计算**问题**计算5! 5 x 4 x 3 x 2 x 1递归思路5! 5 × 4! 4! 4 × 3! 3! 3 × 2! 2! 2 × 1! 1! 1 (这就是基线条件)实战publicclassFactorial{publicstaticintfactorial(intn){// 基线条件1的阶乘是1if(n1){return1;}// 递归条件n的阶乘 n × (n-1)的阶乘returnn*factorial(n-1);}publicstaticvoidmain(String[]args){System.out.println(5的阶乘是factorial(5));// 输出120}}执行过程factorial(5) → 5 × factorial(4) factorial(4) → 4 × factorial(3) factorial(3) → 3 × factorial(2) factorial(2) → 2 × factorial(1) factorial(1) → 1 (基线条件开始返回) 然后逐层返回2×12, 3×26, 4×624, 5×241202.2 案例二斐波那契数列**问题**11235813…每个数是前两个数之和递归思路fib(5) fib(4) fib(3) fib(4) fib(3) fib(2) fib(3) fib(2) fib(1) fib(2) 1 (基线条件) fib(1) 1 (基线条件)实战publicclassFibonacci{publicstaticintfib(intn){// 基线条件前两项都是1if(n1||n2){return1;}// 递归条件当前项 前两项之和returnfib(n-1)fib(n-2);}publicstaticvoidmain(String[]args){for(inti1;i10;i){System.out.print(fib(i) );// 输出1 1 2 3 5 8 13 21 34 55}}}2.3 目录遍历**问题**计算一个文件夹及其所有子文件夹中的文件总数实战importjava.io.File;publicclassDirectoryCounter{publicstaticintcountFiles(Filedirectory){// 基线条件如果是文件返回1if(directory.isFile()){return1;}// 递归条件如果是文件夹遍历其内容intcount0;File[]filesdirectory.listFiles();if(files!null){for(Filefile:files){countcountFiles(file);// 递归调用}}returncount;}publicstaticvoidmain(String[]args){FiledirnewFile(C:\\Users\\QuienFun\\Documents);System.out.println(文件总数countFiles(dir));}}3.深入理解递归为什么会栈溢出3.1 什么是栈在计算机中栈是一种遵循LIFO原则的数据结构即后进先出。想象一个堆叠的盘子你只能从最顶部拿走或放入一个盘子。最后放上去的盘子会最先被拿走。或者说一堆叠放的书你只能从最上面取书或放书。在 Java或者说大多数编程语言的程序运行时环境中有一个非常重要的内存区域叫做“调用栈”。Java 虚拟机栈每当一个线程开始运行时JVM 都会为它分配一块私有的内存空间其中就包括Java 虚拟机栈。这个栈用于跟踪每个方法的调用。工作原理方法调用当一个方法比如main方法被执行时JVM 会在栈顶为这个方法创建一个新的内存块称为“栈帧”。栈帧内容这个栈帧里存储了关于这次方法调用的所有信息局部变量方法内部声明的变量。操作数栈用于执行字节码指令时的临时数据存储。动态链接指向运行时常量池中该方法的引用。方法返回地址方法执行完毕后应该回到哪里继续执行。方法结束当方法执行完毕遇到return语句或执行到最后它的栈帧就会被销毁从栈顶弹出。程序的控制权会返回到调用它的地方即上一个栈帧中记录的返回地址。这个过程就像一个栈的操作push调用方法压入一个新的栈帧。pop方法结束弹出栈帧。结合之前的阶乘例子看栈的变化让我们用factorial(3)来可视化栈的变化过程步骤栈状态 (栈底 - 栈顶)说明1[main]程序开始main方法入栈。2[main, factorial(3)]main调用factorial(3)factorial(3)的栈帧被压入。3[main, factorial(3), factorial(2)]factorial(3)调用factorial(2)factorial(2)入栈。4[main, factorial(3), factorial(2), factorial(1)]factorial(2)调用factorial(1)factorial(1)入栈。5[main, factorial(3), factorial(2)]factorial(1)达到基准情况返回1。它的栈帧被弹出销毁。6[main, factorial(3)]factorial(2)得到结果2*12返回2。它的栈帧被弹出销毁。7[main]factorial(3)得到结果3*26返回6。它的栈帧被弹出销毁。8[main]main方法继续执行最终结束。3.2 什么是栈溢出栈溢出是指程序运行过程中调用栈的大小超过了系统所分配给它的内存限制。当发生栈溢出时JVM 会抛出一个错误java.lang.StackOverflowError。为什么会发生栈溢出最主要的原因就是无限递归或递归深度过大。无限递归如果一个递归函数缺少了正确的基准情况或者基准情况永远无法达到那么它就会无休止地调用自己。每一次调用都会在栈上创建一个新的栈帧栈空间会被迅速耗尽最终导致StackOverflowError。// 栈溢出publicstaticvoidinfiniteRecursion(){infiniteRecursion();// 没有基准情况永远在调用自己}递归深度过大即使有正确的基准情况但如果要解决的问题规模非常大递归的深度也会非常深。例如计算factorial(100000)。虽然逻辑上正确但100,000个栈帧会轻易超过默认的栈大小限制。3.3 如何避免栈溢出确保基准情况正确并且可以到达使用循环迭代代替递归使用尾递归优化这是一种特殊的递归形式不过标准的 Java 编译器javac目前并不支持尾递归优化所以这里不过多讲解学习它的思路。什么是尾递归尾递归是指递归调用是函数体中最后执行的语句并且返回值不被修改。这样编译器就可以重用当前的栈帧而不是创建一个新的。普通递归非尾递归// 在 return 之后还有一个乘法操作 n * ...returnn*factorial(n-1);这里在计算factorial(n-1)之前必须先知道n的值所以必须保留当前栈帧来等待结果。尾递归版本publicstaticlongfactorialTailRecursive(intn){returnfactorialHelper(n,1);}// 辅助方法accumulator 是累积器用于存储中间结果privatestaticlongfactorialHelper(intn,longaccumulator){// 基准情况if(n0){returnaccumulator;}// 尾递归调用它是最后一步操作并且把结果直接返回else{returnfactorialHelper(n-1,n*accumulator);}}在这串代码中factorialHelper(n-1, n * accumulator)是最后一步JVM 理论上可以优化它不创建新栈帧。但由于 Java 标准编译器不支持它仍然会栈溢出。但在支持的语言中这就避免了栈溢出的问题。增加栈大小作为最后的手段可以通过 JVM 参数-Xss来增加每个线程的栈大小。例如-Xss2m将栈大小设置为 2MB。但这只是延迟了问题并没有从根本上解决无限递归或大深度递归的效率问题。4.结尾递归的优缺点代码简洁逻辑清晰可以解决特定问题递归的缺点性能开销大栈溢出风险调试困难能用循环解决尽量用循环解决虽然递归缺点明显但是它的思想值得我们学习把大的问题分成小的问题解决。⭐ 如果这对你有帮助不妨收藏和分享一下