zoukankan      html  css  js  c++  java
  • 浅谈递归

    定义

    英文定义:Recursion is the process of repeating items in a self-similar way.

    具体到计算机中去:

    递归:又称为递回,在数学和计算机科学中,是指在函数的定义中使用函数自身的方法.

    [以上定义来源为wiki].

    英文的Recursion表达的是重复发生,再次重现的意思.而对应的中文翻译”递归”确表达了两个意思:” 递”+”归”.这两个意思,正是递归思想的精髓.

    递归思想

    递归的基本思想是:把规模大的问题转化为规模小的相似的子问题来解决.这种思想与数学中的归纳法十分相近。

    递归在程序中的表现:在函数实现递归时,因为解决大问题的方法和解决小问题的方法往往是同一个方法,所以就产生了函数调用它自身的情况.需要注意的是,递归函数必须有明显的结束条件(base case),避免产生无限递归的情况.

    经典示例:N的阶乘问题

        我们知道5!=5*4*3*2*1;4!=4*3*2*1;…

                5!=5*4!...

        从上面我们可以归纳出N!=N*(N-1)!,由此函数的代码如下:

    public static long factorial(int n){
    
          if(n==1) // base case
    
             return 1;
    
          else  // reduction step
    
             return n*factorial(n-1);
    
       }

    Base case:基线条件,递归的终止条件

    Reduction step:递归操作,对问题的定义.

    递归vs循环

             从大多数情况来看,递归和循环是可以相互转换的.例如上面的阶乘问题,如果计算的阶乘的方法是n!=n*(n-1)*….*2*1,就明显是一个循环的过程,如果计算阶乘的方法是n=n*(n-1)!,这就归纳出了一个递归定义。循环要求思考一个问题怎么做,而递归要求思考一个问题的定义(某Lisp开发者说的).

             实际开发中,循环更加常见,但有些情况下,可能需要将循环转为递归(面试).递归与循环其实想通的地方挺多,它们都需要初始条件和终止条件.用递归解决循环问题,需要明确基线条件和定义递归问题(每一步递归操作的算法),本人在面试过程中遇到过一个这样问题:求两个数的最大公约数,不使用循环.其实,从后面那句”不使用循环”就明显是考递归的。Java代码如下:

    // 辗转相除法求两个数的最大公约数
    
       public static int gcd(int num1,int num2){
    
          // check args
    
          if(num1==0 || num2==0){
    
             return 0;
    
          }
          // num1>num2
    
          int tmp = num1;
    
          num1 = Math.max(num1,num2);
    
          num2 = Math.min(tmp, num2);
    
          int p = num1%num2;
    
          /*while(p!=0){
    
             num1 = num2;
    
             num2 = p ;
    
             p = num1%num2;
    
          }*/
    
          if(p!=0)
    
             return gcd(num2,p);
    
          return num2;
       }

    从上面可以看到,循环转递归时要把握两个点:

    • 循环的终止条件与递归中的基线条件
    • 循环中的算法与递归的算法

    应用

    当问题的定义本身就是递归形式的时候,可以考虑使用递归.数据结构里面的“树”就是典型的递归定义。还有如上面说的阶乘,汉诺塔问题,斐波拉契数列等.都可以使用来递归解决问题.

    使用递归可以使用紧凑和优雅的代码来解决看起来很”吓人”的问题.但实际中,递归并不十分常见.首先,函数执行过程中,栈空间的分配是有限的,如果递归的深度过大,就会造成栈空间的溢出.其次,递归提高了程序的书写效率,但并不意味着执行效率的提高.一般来说,递归的执行效率未必比循环高,因为在栈上开辟新空间、分配局部变量都是需要时间开销的,特别是递归深度很大的时候。所以,学习递归主要是学习其思想—把规模大的问题转化为规模小的相似子问题来解决。

    借鉴知乎上某资深码农的一句话,个人觉得说的很好:

             当你的问题分析透彻,何时使用递归就会自然明了。递归只是一种函数调用的技巧,不是解决问题的公式。一个问题可以用递归做,也可以不用递归做,递归不是必须的。所以,理解你需要解决的问题,想清楚解决问题的步骤方法,你会突然发现,原来可以用递归来简化问题。不要一开始就想着如何用递归。

    参考资料:

       http://zh.wikipedia.org/wiki/%E9%80%92%E5%BD%92

       http://www.zhihu.com/question/20507130

       http://www.zhihu.com/question/20096035

       http://www.ibm.com/developerworks/cn/linux/l-recurs.html

  • 相关阅读:
    Python_day1
    12/04
    Linux基础笔记
    八:动态规划-未名湖边的烦恼
    七:动态规划-数字三角形
    六:大数运算-减法运算
    五:大数运算-加法运算
    四:大数运算-乘法运算
    三:排序-幸运数字
    二:排序-果园
  • 原文地址:https://www.cnblogs.com/heavenyes/p/3822663.html
Copyright © 2011-2022 走看看