递 归

递归的定义:在数学和计算机科学中,递归是指在函数的定义中调用函数自身的方法。

递归举例:斐波那契数列。

>>> def fib(n):
...     '''This is Fibonacci by Recursion'''
...     if n == 0:    # 函数结束的条件
...         return 0
...     elif n == 1:
...         return 1
...     else:
...         return fib(n-1) + fib(n-2)    # 递归,定义函数的内部调用函数自身
...
>>> fib(10)
55

  从上面的计算过程可以看出,每个递归的过程都是向着最初的已知条件(结束条件)方向挺近一步,直到通过这个最底层的条件得到结果,然后再一层一层向上回馈(返回)计算结果。

       将上面的例子优化:

>>> m = {0:0, 1:1}    # n=1和n=0的值已知,直接定义出来,省去每次的多个判断
>>> def fib(n):
...     if not n in m:
...         m[n] = fib(n-1) + fib(n-2)
...     return m[n]
...
>>> fib(10)
55

  递归的要素:

              结束条件:满足条件时,函数调用结束,直接返回一个值,即有一个明确的递归结束条件,称为递归出口。

              递归调用:包含一个或多个调用,这些调用旨在解决问题的一部分。

         这里的关键是,通过一次次的分解问题,将问题分解为较小的部分,最终分解到结束条件。

  递归的回溯与递推:

    递推:像上边递归实现所拆解,递归每一次都是基于上一次进行下一次的执行,这叫递推;

    回溯:则是在遇到终止条件,则从最后往回返,一级一级的把值返回,这叫回溯。

>>> lst =[1, 2, [3, [4, 5, 6, [7, 8, [9, 10, [11, 12, 13, [14, 15,[16,[17,]],19]]]]]]]
>>> def search(lst):
...     for item in lst:
...         if type(item) is list:
...             search(item)
...         else:    # 递归调用结束条件
...             print(item, end=' ')    
...
>>> search(lst)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 19    

  深刻理解:每次递归调用函数时,都将为这个递归调用的函数创建一个新的命名空间。各自的命名空间是相互独立的,每个递归函数在各自的命名空间内生存并依次按顺序执行;当执行到结束条件时,从结束的位置开始向前一次次的反馈(返回)并继续执行每个命名空间内剩余的部分。关键在于每次递归时都会创建各自相互独立的命名空间。

  两个经典案例:阶乘和幂

    阶乘:1的阶乘为1;

            对于大于1的数字n,其阶乘为n-1的阶乘再乘以n。

>>> def func(n):
...     if n == 1:
...         return 1
...     return n * func(n-1)
...
>>> func(5)
120

  幂:对于任何数字x,power(x, 0)都为1;

    n>0时,power(x, n)为power(x, n-1)与x的乘积。

>>> def func(n, m):
...     if m == 0:
...         return 1
...     if m < 0:
...         return (1/n) * func(n, m+1)
...     else:
...         return n * func(n, m-1)
...
>>> func(3, 3)
27
>>> func(3, 1)
3
>>> func(3, 0)
1
>>> func(3, -1)
0.3333333333333333
>>> func(3, -2)
0.1111111111111111
>>> func(3, -3)
0.037037037037037035

递归的特点:

         必须有一个明确的结束条件;

         每次进入更深一层递归时,问题的规模相比上次递归都应有所减少;

         递归效率不高,递归层次太多,会导致栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的。每当进入一个函数调用,栈就会增加一层栈帧;每当函数返回,栈就会减少一层栈帧。由于栈的大小不是无限的,所以递归调用次数过多,会导致栈溢出。

递归的优缺点:

  优点:

    递归使代码看起来更加整洁、优雅;

    可以用递归将复杂任务分解成更简单的子问题;

    使用递归比使用一些嵌套迭代更容易。

  缺点:

    递归的逻辑很难调试、跟进;

    递归调用的代价高昂(效率低),因为占用了大量的内存和时间。

  注意:Python3默认递归的深度不能超过997层。

 

  尾递归调用优化:

    在函数的最后一步进入递归调用。这样每层函数的调用就直接结束了,不会去等待下一次递归调用的返回值,这样每次执行完毕进入下一次后,就不会占用内存去保存当前层的状态,节省内存空间。

 

posted @ 2018-09-18 19:10  从python开始  阅读(289)  评论(0编辑  收藏  举报