首页 > 其他分享 >循环?还是递归?

循环?还是递归?

时间:2023-05-03 14:32:54浏览次数:33  
标签:Java 还是 递归 System 循环 java out


循环?还是递归?_java


前言


前几天群里有几个小伙伴,展开了如下讨论:


--------------------------------------------------------------------------

【西安-Java-小白】

    谁遇上过?

循环?还是递归?_java_02


【杭州-Java-JOEL】


    你要打断点看哪行出错了


【西安-Java-小白】


    栈溢出,mybatis执行查询的时候,循环查询,1000条查询一次,到160多次的时候栈溢出


【北京-Android-背影】


    你有递归么


【西安-Java-小白】


    嗯。刚把递归干掉了,换成循环试试。


【北京-Android-背影】


    @西安-Java-小白 你去掉递归还会报错么


    一般栈溢出都是有递归调用方法体导致的


【西安-Java-小白】


    嗯


    去掉了,在测试


    换成while了


【北京-Android-背影】


    嗯嗯,等会给我们说下结果


【西安-Java-小白】


    感觉速度比递归快太多了


【杭州-Java-JOEL】


    查询的时候批量去查稍微好点


【北京-Android-背影】


    递归方法体内的变量会一直保存,但是有的变量没任何意义。


    循环的话,方法体内的变量会被回收掉,不会一直占内存。


【杭州-Java-JOEL】


    这个解释赞


【西安-Java-小白】


    嗯呢


    改成循环就OK了


【西安-Java-xcbeyond】


    栈,主要是用来存放栈帧的,每执行一个方法就会出现压栈操作,所以采用递归的时候产生的栈帧比较多,递归就会影响到内存,非常消耗内存。而使用循环就执行了一个方法,压入


栈帧一次,只存在一个栈帧,所以比较节省内存。


--------------------------------------------------------------------------



    


    都是递归惹的祸! 接下来,我们就一起讨论下递归和循环吧,该如何用,他们都有哪些区别呢? 时间复杂度,空间复杂度又是多少呢



循环、递归验证


    循环: 当满足某一条件时,进行反复执行某一操作(循环体)。 如: for、while循环


    递归: 在一个方法内调用方法本身,并且要有递归结束的判断。



    循环和递归两者之间是可以相互替换实现的,但他们之间却有很大的差异,其时间复杂度,空间复杂度有着很大的差异的。



    接下来,我们就直接撸起代码见效果吧,以一个整数递减到0输出为例。


package com.xcbeyond.test;	

	
/**	
 * 递归测试	
 * @Auther: xcbeyond	
 * @Date: 2019/9/12 11:26	
 */	
public class RecursionTest {	

	
    public static void main(String [] args) {	
        int number = 5000;	

	
        long startTime = System.currentTimeMillis();	
        loop(number);	
        long endTime = System.currentTimeMillis();	
        System.out.println();	
        System.out.println("循环耗时:" + (endTime-startTime));	

	

	
        long startTime2 = System.currentTimeMillis();	
        recursion(number);	
        long endTime2 = System.currentTimeMillis();	
        System.out.println();	
        System.out.println("递归耗时:" + (endTime2-startTime2));	
    }	

	
    /**	
     * 递归	
     * @param n	
     * @return	
     */	
    public static void recursion(int n) {	
        if(n <= 0) {	
            return;	
        }else {	
            System.out.print(n + " ");	
            recursion(n - 1);	
        }	
    }	

	
    /**	
     * 循环	
     * @param n	
     * @return	
     */	
    public static void loop(int n) {	
        for(int i = n; i> 0;i--) {	
            System.out.print(i + " ");	
        }	
    }	
}



结果分析:


number分别为1000、5000、10000,其耗时结果如下:


情况1:1000



循环耗时:34	
递归耗时:18


情况2:5000



循环耗时:48	
递归耗时:11


情况3:10000


循环耗时:142	
Exception in thread "main" java.lang.StackOverflowError	
at sun.nio.cs.UTF_8$Encoder.encodeLoop(UTF_8.java:691)	
  at java.nio.charset.CharsetEncoder.encode(CharsetEncoder.java:579)	
  at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:271)	
  at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:125)	
  at java.io.OutputStreamWriter.write(OutputStreamWriter.java:207)	
  at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:129)	
  at java.io.PrintStream.write(PrintStream.java:526)	
  at java.io.PrintStream.print(PrintStream.java:669)	
  at com.xcbeyond.test.RecursionTest.recursion(RecursionTest.java:36)



    从上述结果来看,使用循环算法比递归算法更耗时,但当循环、递归次数达到一定数据级时,递归算法就会出现栈溢出(StackOverflowError)问题了,这也就是文章开头说的现象了。



    针对栈溢出问题,我们可以进一步来跟踪如下:


(上述代码略做修改,为了便于观察,number设置为5)


package com.xcbeyond.test;	

	
/**	
 * 递归测试	
 * @Auther: xcbeyond	
 * @Date: 2019/9/12 11:26	
 */	
public class RecursionTest {	

	
    public static void main(String [] args) {	
        int number = 3;	

	
        long startTime = System.currentTimeMillis();	
        loop(number);	
        long endTime = System.currentTimeMillis();	
        System.out.println();	
        System.out.println("循环耗时:" + (endTime-startTime));	

	

	
//        long startTime2 = System.currentTimeMillis();	
//        recursion(number);	
//        long endTime2 = System.currentTimeMillis();	
//        System.out.println();	
//        System.out.println("递归耗时:" + (endTime2-startTime2));	
    }	

	
    /**	
     * 递归	
     * @param n	
     * @return	
     */	
    public static void recursion(int n) {	
        Thread.currentThread().dumpStack();	
        if(n <= 0) {	
            return;	
        }else {	
            System.out.print(n + " ");	
            recursion(n - 1);	
        }	
    }	

	
    /**	
     * 循环	
     * @param n	
     * @return	
     */	
    public static void loop(int n) {	
        for(int i = n; i> 0;i--) {	
            System.out.print(i + " ");	
            Thread.currentThread().dumpStack();	
        }	
    }	
}



循环的栈分配情况:

循环?还是递归?_System_03

递归的栈分配情况:

循环?还是递归?_递归_04


    通过分析栈的出栈入栈过程,循环只会堆栈一次,而递归却随着递归数次累积堆栈,即:随着递归次数增多,将会出现栈溢出的问题。



循环、递归区别


循环


    优点: 结构简单


    缺点: 并不能解决所有的问题。 有的问题适合使用递归而不是循环,如果使用循环并不困难的话,最好使用循环。


递归


    优点: 代码简洁、清晰,并且容易验证正确性


    缺点: 它的运行需要较多次数的方法调用,如果调用层数比较深,需要增加额外的堆栈处理,比如参数传递需要压栈等操作,会对执行效率有一定影响。 但是,对于某些问题,如果不使用递归,那将是极端难看的代码。



    一般递归调用可以处理的算法,也通过循环去解决常需要额外的低效处理 。 现在的编译器在优化后,对于多次调用的方法处理会有非常好的效率优化,效率未必低于循环。



总结


    每次的递归,就是方法的每次调用,即: 进行多次压栈操作。 所以,如果使用递归算法,则不能递归次数不能过大,复杂将会出现栈溢出。


    栈,主要是用来存放栈帧的,每执行一个方法就会出现压栈操作,所以采用递归的时候产生的栈帧比较多,递归就会影响到内存,非常消耗内存。而使用循环就执行了一个方法,压入栈帧一次,只存在一个栈帧,所以比较节省内存。



总之,在循环、递归算法的选取上,可遵循如下原则:


  • 循环次数不是特别大,处理逻辑及其复杂,如果用循环算法,可能难于理解时,可优先采用递归算法。
  • 处理逻辑简单,则用循环。


    记住一点,无论使用循环,还是递归,尽量避免出现循环次数特别大的场景处理,尽量去规避它吧。




循环?还是递归?_java_05



标签:Java,还是,递归,System,循环,java,out
From: https://blog.51cto.com/xcbeyond/6241317

相关文章

  • 【web 开发基础】PHP 中的递归函数
    前言什么是递归?递归做为一种算法在程序设计语言中广泛应用。所谓的递归简单地概括就是程序调用自身的编程技巧称为递归(recursion)。递归在计算机科学中是指一种通过重复将问题分解为同类的子问题而解决问题的方法。递归式方法可以被用于解决很多的计算机科学问题,因此它是计算机科学......
  • java基础-流程控制语句,顺序结构、分支结构、循环结构
    一、循序结构顺序结构语句是java程序默认的执行流程,按照代码的先后顺序,从上到下依次执行。二、分支结构-if、switch1、if的三种语法结构//1if(关系表达式){表达体内容;}//2if(关系表达式1){表达体内容;}elseif(关系表达式2){表达体内容;}//3if(......
  • 范德蒙德矩阵行列式 & 循环矩阵行列式的证明
    范德蒙德矩阵的行列式\[\begin{vmatrix}1&1&1&\dots&1\\x_1&x_2&x_3&\dots&x_n\\x_1^2&x_2^2&x_3^2&\dots&x_n^2\\\vdots&\vdots&\vdots&\ddots&\vdots\\......
  • 废弃P-value,还是学学如何评估统计检验结果?
    前几天,Nature上一篇comment再度引发关于p-value如何使用和解释的文章:Scientistsriseupagainststatisticalsignificance,800多名科学家联合声明拒绝使用基于p-value或置信区间或贝叶斯因子等的二分法将研究结果分为统计显著和统计不显著两个部分,而是应该把置信区间改为兼容性区......
  • STATA 循环应用
    gentz=0localvvpid_a_c1pid_a_c2pid_a_c3pid_a_c4pid_a_c5pid_a_c6pid_a_c7pid_a_c8pid_a_c9pid_a_c10localk=_Nforvaluesi=1/`k'{localmm=0foreachvarofvarlist`vv'{localmm=`mm'+1ifpid[`i']==`var'[`i......
  • c语言数据结构-----循环队列
    #include<stdio.h>#include<stdlib.h>#defineMAXSIZE10//循环队列长度为m-1时即为满typedefstruct{ intfront; intrear; int*base;}SqQueue;//初始化队列intInitQueue(SqQueue&q){ q.base=newint[MAXSIZE]; q.front=q.rear=0; return0;}//求队列长度int......
  • Theano 中文文档 0.9 - 7.2.5 循环
    7.2.5循环译者:Python文档协作翻译小组,原文:Loop。本文以CCBY-NC-SA4.0协议发布,转载请保留作者署名和文章出处。Python文档协作翻译小组人手紧缺,有兴趣的朋友可以加入我们,完全公益性质。交流群:467338606。Scan重复的一般形式,可用于循环。Reduction和map(在前面的维度上循环)是sc......
  • 公众号还是博客?
    好久没编程,最近得闲又写着玩,不可避免网上搜索答案,发现中文互联网真的是越来越玩完了。这个观点,也是受马督工的影响,和过去相比,中文互联网上,真正有价值的东西,越来越少。占据内容大头的,是长视频、短视频,我也是b站,腾讯视频的深度用户,一不小心就刷几个小时,但不得不承认,基本上都是浪费时......
  • kube-scheduler的2个独立控制循环
    k8s1.15.0调度周期:从NextPod到RunPermitPlugins绑定周期:从RunPrebindPlugins到RunPostbindPlugins调度的本质就是将Pod为空的NodeName写上相应的Node的值第1个控制循环:InformerPath通过Informer来ListWatchAPI对象,把待调度Pod(nodeName字段是空的)添加进调度队列。只有对调......
  • 初识循环语句
     while循环 首先创建一个想要循环的变量设置变量循环的值当变量的值小于设定的值时,将会一直循环,(后面添加变量名称,就可以显示循环次数)每循环一次就往上加 直至循环次数达到后将跳出循环......