Logo

郎哥编程

递归函数

2020-12-29 145

递归的概念比较抽象,还是从一个案例入手,逐渐剥开递归的抽象面纱。

递归函数的实现

假如准备要编写一个计算自然数n阶乘的程序。在编写程序之前,需要了解什么是自然数n的阶乘?自然数n的阶乘是所有小于及等于该数的自然数的积。

例如自然数6的阶乘运算是:1 X 2 X 3 X 4 X 5 X 6 = 720。

程序要求给定一个自然数n,计算自然数n的阶乘,并将计算结果输出到控制台。如何用程序来求自然数n的阶乘呢?

                                             01.jpg

观察上面的阶乘案例发现,n的阶乘等于n乘以(n-1)的阶乘。例如:4的阶乘等于4乘以(4-1)的阶乘;5的阶乘等于5乘以(5-1)的阶乘。(n-1)的阶乘又等于(n-1)乘以(n-2)的阶乘。例如:(4-1)的阶乘等于(4-1)乘以(4-2)的阶乘;(5-1)的阶乘等于(5-1)乘以(5-2)的阶乘。

因此求n的阶乘可以写为:

n!= n * (n-1)!
= n * (n-1) * (n-2)!
= n * (n-1) * (n-2) * (n-3)!
…………
= n * (n-1) * (n-2) * (n-3) *(n-4) …… * (n-(n-2)) * 1!

上面的算式对n的阶乘进行层层分解:第一层分解将n的阶乘分解为n与(n-1)阶乘的积;第二层将(n-1)的阶乘分解为(n-1)与(n-2)阶乘的积;第三层将(n-2)的阶乘分解为(n-2)与(n-3)阶乘的积。以此类推,阶乘规模越来越小,最终就是计算1的阶乘。

例如求4的阶乘:

4! = 4 * (4-1)!
= 4 * (4-1) * (4-2)!
= 4 * (4-1) * (4-2) *1!

求4的阶乘采用上面的计算方法,进行三层分解即可。

第一层:4的阶乘分解为4与(4-1)阶乘的积;

第二层:(4-1)阶乘分解为(4-1)与(4-2)阶乘的积;

第三层:(4-2)阶乘分解为(4-2)与(4-3)阶乘的积,也就是(4-2)与1阶乘的积。

经过三层分解,将计算规模为4的阶乘分解为计算规模为1的阶乘,剩余的就是四则运算了。

有了计算阶乘的算法,现在就可以编写程序了。下面给出程序流程图:

02.jpg

在上面的流程图中,把计算n的阶乘封装到factorial(n)函数中,该方法计算传入参数n的阶乘,在方法内部对n的阶乘进行分解,当n=1时直接返回1,因为1的阶乘是1,反之,将n的阶乘分解为n与(n-1)阶乘的积,求(n-1)的阶乘,还是调用factorial(n-1)方法。

从流程图可以看出,在factorial(n)函数内部又调用了factorial(n-1)函数,这种在方法内部又调用自身函数的结构称为递归结构,该方法也称为递归函数。

递归有递推和回归的意思,递推就是把复杂的任务不断分解,让问题规模越来越小,当问题规模小到能够直接解答时,递推就结束了。递推的结束预示着回归的开始,回归从递推结束时问题的解答开始回归,由最简问题的解答按照递推路径反方向获取整个问题的解答。

其实递归蕴含的思想类似我们学过的数学归纳法:为了求解问题p(n),需要先求解基础问题p(1),然后再求解p(n-1),最后获得p(n)的解。

递归结构如下图所示:

06.png

递归方法内部分为递归出口和递归体两部分:递归出口是递归的终止条件,一般由条件判断语句和递归终止语句块组成;递归体是包含调用自身方法的语句块。

例如求n的阶乘递归函数可以这么写:

def factorial(n):
  if n == 1:
    return 1
  else:
    return n * factorial(n-1)

求n的阶乘的递归函数名称是factorial,参数是待求阶乘的自然数n。递归出口部分判断n是否为1,如果为1则返回1,因为1的阶乘结果是1。

递归体部分再次调用自身方法对n的阶乘规模进行分解,逐渐降低n的阶乘规模,直至n变为1,满足递归出口的条件。

案例代码:

def factorial(n):
   if n == 1:
    return 1
   else:
    return n * factorial(n-1)
if __name__== "__main__":
   # 计算自然数6的阶乘
   print(factorial(6))
   # 计算自然数20的阶乘
   print(factorial(20))

递归与栈结构

递归是通过栈(stack)这种数据结构来实现的,栈结构是一种特殊的线性表,限定仅在表的一端进行元素的插入和删除。当表中没有元素时,称为空栈。若给定栈:

S = (a1,a2,……,an)

则称a1是栈底元素,an是栈顶元素,表中元素按a1,a2,……,an的次序进栈,出栈的顺序是an,……,a2,a1。也就是说,栈结构的元素访问原则是后进先出,也称为后进先出的线性表,如下图所示。

03.jpg

递归方法每调用一次,Python解释器就会把当前递归函数涉及的参数、局部变量、返回值等数据在栈中压入一个栈帧,一个栈帧保存了函数的参数、局部变量、中间运算结果、返回值等数据。每当递归方法返回时,Python解释器会将当前栈帧弹出并释放掉,并把返回值返回给上层调用者。

07.png

用循环替代递归

由于栈空间的内存大小是有限的,当递归调用的次数过多时,会导致栈溢出问题(栈的内存空间不足)。考虑到递归存在栈溢出的问题,在一些情况下,可以使用循环结构来代替递归结构。

下面的案例代码使用循环结构来计算自然数n的阶乘。

案例代码:

def factorial(n):
  fac = 1
  for i in range(1,n + 1):
    fac = fac * i
  return fac
 
if __name__== "__main__":
 
  # 计算自然数6的阶乘
  print(factorial(6))
  # 计算自然数20的阶乘
  print(factorial(20))

上机操作

编写一个程序,递归计算斐波那契数列。

斐波那契数列以递推的方法定义:

F(1)=1,F(2)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 3,n ∈ N*)

案例代码:

def fib_recur(n):
  if n <= 1:
    return n
  return fib_recur(n-1) + fib_recur(n-2)
 
for i in range(1,20):
   print(fib_recur(i),end=' ')

 

代码在线纠错(通义千问 qwen-max)

支持粘贴多个代码文件,提交后由阿里云通义千问自动分析代码漏洞、语法错误、逻辑问题并给出修改建议。
您已解锁 AI 代码纠错功能,可正常使用!

评论区

登录 后发表评论
暂无评论