文章目录

  • 递归概念
  • 递归要素
  • 递归与迭代的区别
  • 示例一:阶乘
  • 示例二:斐波那契数列
  • 示例三:汉诺塔问题
  • 尾递归
  • Python 中尾递归的解决方案

递归概念

递归:程序调用自身的编程技巧称为递归( recursion)。用一种通俗的话来说就是自己调用自己,它通常把一个大型复杂的问题层层转化为一个与原问题相似的、但是规模较小的问题来求解,当问题小到一定规模的时候,需要一个递归出口返回。递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。

递归函数:在编程语言中,函数直接或间接调用函数本身,则该函数称为递归函数;在数学上的定义如下:对于某一函数 f(x)f(x)f(x),其定义域是集合 A,那么若对于 A 集合中的某一个值 X0X_0X0​,其函数值 f(x0)f(x_0)f(x0​) 由 f(f(x0))f(f(x_0))f(f(x0​)) 决定,那么就称 f(x)f(x)f(x) 为递归函数。


递归要素

  • 递归必须包含一个基本的出口(结束条件),否则就会无限递归,最终导致栈溢出;

  • 递归必须包含一个可以分解的问题,例如要想求得 fact(n)fact(n)fact(n),就需要用 n∗fact(n−1)n * fact(n-1)n∗fact(n−1);

  • 递归必须必须要向着递归出口靠近,例如每次递归调用都会 n−1n-1n−1,向着递归出口 n==0n == 0n==0 靠近。


递归与迭代的区别

  • 递归(recursion):递归则是一步一步往前递推,直到递归基础,寻找一条路径, 然后再由前向后计算。(A调用A)

  • 迭代(iteration):迭代是重复反馈过程的活动,其目的通常是为了逼近所需目标或结果。每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值,因此迭代是从前往后计算的。(A重复调用B)


示例一:阶乘

一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且 0 的阶乘为 1。即 n!=1×2×3×...×(n−1)×nn!=1×2×3×...×(n-1)×nn!=1×2×3×...×(n−1)×n,以递归方式定义:n!=(n−1)!×nn!=(n-1)!×nn!=(n−1)!×n

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

示例二:斐波那契数列

斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家莱昂纳多·斐波那契以兔子繁殖为例子而引入,故又称为“兔子数列”。

有一个数列:0、1、1、2、3、5、8、13、21、34、55、89…,这个数列从第3项开始,每一项都等于前两项之和。以递推的方法定义:F(n)=F(n−1)+F(n−2)(n≥3,n∈N∗)F(n)=F(n - 1)+F(n - 2)(n ≥ 3,n ∈ N^*)F(n)=F(n−1)+F(n−2)(n≥3,n∈N∗)

def fibonacc(n):if n == 1 or n == 2:return 1else:return fibonacc(n-1) + fibonacc(n-2)

以上方法的时间复杂度为O(2n)O(2^n)O(2n),稍微大一点的数都会算很久,有一个简单的解决方案,使用 lru_cache 缓存装饰器,缓存一些中间结果:

from functools import lru_cache# 缓存斐波那契函数已经计算出的结果,最多占用1024字节内存
@lru_cache(maxsize=1024)
def fibonacc(n):if n == 1 or n == 2:return 1else:return fibonacc(n-1) + fibonacc(n-2)

另外还有更加节省时间和空间的方法:

def fibonacc(n, current=0, next=1):if n == 0:return currentelse:return fibonacc(n-1, next, current+next)

示例三:汉诺塔问题

汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。64片黄金圆盘移动完毕之日,就是世界毁灭之时。

对于 n 个盘子,移动步骤如下:

  • 把 n-1 个盘子由 A 经过 C 移动到 B
  • 把最后一个盘子移动到 C
  • 把 n-1 个盘子由 B 经过 A 移动到 C

递归代码实现:

def hanoi(n, a, b, c):                                # n 个盘子,a,b,c三个柱子if n > 0:hanoi(n-1, a, c, b)                           # 把 n-1 个盘子由 a 经过 c 移动到 bprint('moving from {0} to {1}'.format(a, c))  # 把最后一个盘子移动到 chanoi(n-1, b, a, c)                           # 把 n-1 个盘子由 b 经过 a 移动到 c

示例:

def hanoi(n, a, b, c):if n > 0:hanoi(n-1, a, c, b)print('moving from {0} to {1}'.format(a, c))hanoi(n-1, b, a, c)hanoi(3, 'A', 'B', 'C')
moving from A to C
moving from A to B
moving from C to B
moving from A to C
moving from B to A
moving from B to C
moving from A to C

尾递归

如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的。通俗来讲就是递归调用放在了函数的最后。

# 一般递归
def func(n):if n > 0:func(n-1)print(n)# 一般递归
def func(n):if n > 0:return func(n-1) + n# 尾递归
def func(n):a = nif n > 0:a += 1print(a, n)return func(n-1)

对于普通的递归,每一级递归都产生了新的局部变量,必须创建新的调用栈,随着递归深度的增加,创建的栈越来越多,容易造成爆栈。

def normal_recursion(n):if n == 1:return 1else:return n + normal_recursion(n-1)

normal_recursion(5) 执行:

normal_recursion(5)
5 + normal_recursion(4)
5 + 4 + normal_recursion(3)
5 + 4 + 3 + normal_recursion(2)
5 + 4 + 3 + 2 + normal_recursion(1)
5 + 4 + 3 + 3
5 + 4 + 6
5 + 10
15

尾递归基于函数的尾调用,每一级调用直接返回递归函数更新调用栈,没有新局部变量的产生,类似迭代的实现。

def tail_recursion(n, total=0):if n == 0:return totalelse:return tail_recursion(n-1, total+n)

normal_recursion(5) 执行:

tail_recursion(5, 0)
tail_recursion(4, 5)
tail_recursion(3, 9)
tail_recursion(2, 12)
tail_recursion(1, 14)
tail_recursion(0, 15)
15

在 Python,Java,Pascal 等语言中是无法实现尾递归优化的,所以采用了 for,while,goto 等特殊结构以迭代的方式来代替尾递归。


Python 中尾递归的解决方案

使用普通的递归来实现斐波那契数列的计算,代码段如下:

def fibonacc(n, current=0, next=1):if n == 0:return currentelse:return fibonacc(n-1, next, current+next)a = fibonacc(1000)
print(a)

此时会报错,因为超过了最大递归深度(默认深度900-1000左右):

Traceback (most recent call last):File "F:/PycharmProjects/algorithm/fibonacc_test.py", line 57, in <module>a = fibonacc(1000)File "F:/PycharmProjects/algorithm/fibonacc_test.py", line 47, in fibonaccreturn fibonacc(n-1, next, current+next)File "F:/PycharmProjects/algorithm/fibonacc_test.py", line 47, in fibonaccreturn fibonacc(n-1, next, current+next)File "F:/PycharmProjects/algorithm/fibonacc_test.py", line 47, in fibonaccreturn fibonacc(n-1, next, current+next)[Previous line repeated 995 more times]File "F:/PycharmProjects/algorithm/fibonacc_test.py", line 44, in fibonaccif n == 0:
RecursionError: maximum recursion depth exceeded in comparison

如果是递归深度不是很大的情况,可以手动重设递归深度来解决:

import sys
sys.setrecursionlimit(10000)  # 递归深度设置为 10000

如果递归深度非常大,那么就可以采用尾递归优化,但是 Python 官方是并不支持尾递归的(不知道为啥),然而这难不到广大的程序员们,早在 2006 年 Crutcher Dunnavant 就想出了一个解决办法,实现一个 tail_call_optimized 装饰器,原文链接:https://code.activestate.com/recipes/474088/,原代码是 Python 2.4 实现的,用 Python 3.x 实现如下:

# This program shows off a python decorator
# which implements tail call optimization. It
# does this by throwing an exception if it is
# it's own grandparent, and catching such
# exceptions to recall the stack.import sysclass TailRecurseException(BaseException):def __init__(self, args, kwargs):self.args = argsself.kwargs = kwargsdef tail_call_optimized(g):"""This function decorates a function with tail calloptimization. It does this by throwing an exceptionif it is it's own grandparent, and catching suchexceptions to fake the tail call optimization.This function fails if the decorated5function recurses in a non-tail context."""def func(*args, **kwargs):f = sys._getframe()if f.f_back and f.f_back.f_back and f.f_back.f_back.f_code == f.f_code:raise TailRecurseException(args, kwargs)else:while 1:try:return g(*args, **kwargs)except TailRecurseException as e:args = e.argskwargs = e.kwargsfunc.__doc__ = g.__doc__return func

使用该装饰器再来实现比较大的斐波那契数列的计算:

@tail_call_optimized
def fibonacc(n, current=0, next=1):if n == 0:return currentelse:return fibonacc(n-1, next, current+next)a = fibonacc(1000)
print(a)

输出结果:

43466557686937456435688527675040625802564660517371780402481729089536555417949051890403879840079255169295922593080322634775209689623239873322471161642996440906533187938298969649928516003704476137795166849228875

tail_call_optimized 实现尾递归优化的原理:当递归函数被该装饰器修饰后,递归调用在装饰器while循环内部进行,每当产生新的递归调用栈帧时,f.f_back.f_back.f_code == f.f_code: 就捕获当前尾调用函数的参数,并抛出异常,从而销毁递归栈并使用捕获的参数手动调用递归函数,所以递归的过程中始终只存在一个栈帧对象,达到优化的目的。


这里是一段防爬虫文本,请读者忽略。
本文原创首发于 CSDN,作者 TRHX•鲍勃。
博客首页:https://itrhx.blog.csdn.net/
本文链接:https://itrhx.blog.csdn.net/article/details/109322815
未经授权,禁止转载!恶意转载,后果自负!尊重原创,远离剽窃!

Python 算法之递归与尾递归,斐波那契数列以及汉诺塔的实现相关推荐

  1. Python之递归(含斐波那契数列和汉诺塔)

    Python递归 一.什么是递归? 递归就是在调用一个函数的过程中,直接或者间接的调用函数自身这就叫递归. 二.实例 这里讲一下递归的注意点: (1)调用一次函数就会在内存里开辟栈帧空间,在调用结束后 ...

  2. C语言数据结构----递归的应用(斐波拉契数列、汉诺塔、strlen的递归算法)

    本节主要说了递归的设计和算法实现,以及递归的基本例程斐波拉契数列.strlen的递归解法.汉诺塔和全排列递归算法. 一.递归的设计和实现 1.递归从实质上是一种数学的解决问题的思维,是一种分而治之的思 ...

  3. python递归算法(斐波那契数列,汉诺塔、二分法查找)

    # 递归算法的三大特点 # 1.递归过程一般通过函数或子过程来实现 # 2.递归算法在函数或子过程的内部,直接或间接调用自己的算法 # 3.递归算法实际上是把问题转换为规模缩小的同类问题的子问题,然后 ...

  4. python 利用递归算法来完成计算乘阶、斐波那契数列、汉诺塔

    一.求一个整数的阶乘: 1.常用的迭代方法写法: def jc(n):result = nfor i in range(1, n):result *= ireturn resultnu = int(i ...

  5. 简洁笔记-Java数据结构基础(5.递归和斐波那契数列、汉诺塔)

    什么是递归 递归简单来说就是方法调用自己 例子1: 从前有座山,山里有座庙,庙里有个和尚,和尚在讲故事,从前有座山,山里有座庙,庙里有个和尚,和尚在讲故事,从前有座山.. 例子2:美女拿着自己的照片, ...

  6. 经典递归——斐波那契数列,汉诺塔

    斐波那契 汉诺塔 0 1 1 2 3 5 8 13 21 int fibonacci(int a){if(a==0)return 0;else if(a==1)return 1;elsereturn ...

  7. 零基础入门学习Python22-递归2 斐波那契数列和汉诺塔

    数学函数 F(n) = 1 n = 1 F(n) = 1 n = 2 F(n) = F(n-1)+F(n-2) n > 2 问n=20时,F(n)的值是多少??? 迭代实现 def F(n):x ...

  8. python 递归方式实现斐波那契数列

    python 递归方式实现斐波那契数列 import time t1=time.time() def factorial(n):if n==1 or n==2:return 1else:return ...

  9. python装饰器模式带参数_Python进阶(七)----带参数的装饰器,多个装饰器修饰同一个函数和递归简单案例(斐波那契数列)...

    Python进阶(七)----带参数的装饰器,多个装饰器修饰同一个函数和递归简单案例(斐波那契数列) 一丶带参数的装饰器 def wrapper_out(pt): def wrapper(func): ...

最新文章

  1. c语言动态迁移mysql,flask-migrate动态迁移数据库
  2. sql server 在占用服务器内存居高不下怎么办【转】
  3. #pragma once与#ifndef的区别
  4. axios vue 加载效果动画_在vue中通过axios异步使用echarts
  5. hdu 1800 (map)
  6. 设计模式示例_桥梁设计模式示例
  7. java水文模型,分布式水文模型.ppt
  8. Mybatis结合Oracle的foreach insert批量插入报错!
  9. 从源码角度彻底理解ReentrantLock(重入锁)
  10. 为什么找不到使用rem的网站
  11. Redis 常用命令操作
  12. 【Java后台开发规范】--- Null值处理
  13. flex justify-content: space-around;和justify-content: space-evenly;的区别
  14. android通知详解
  15. 手机上有哪些超棒超实用的APP让你觉得相见恨晚
  16. 其实英语实在太简单了
  17. strstr和strcasestr
  18. 实验吧-CTF-web-头有点大貌似有点难看起来有点难(学习笔记)
  19. Android 多进程同时打开相机
  20. VC-VQA: Visual Calibration Mechanism for Visual Question Answering (VQA的视觉校准机制)

热门文章

  1. Atlas应用程序调试技巧
  2. 发现很多人的基础都不好
  3. [密码学基础][每个信息安全博士生应该知道的52件事][Bristol52]50.什么是BLS基于对的签名方案?
  4. [密码学基础][每个信息安全博士生应该知道的52件事][Bristol52]45.描述一些对抗RSA侧信道攻击的防御方法
  5. ajax 微信code获取_获取链接的参数,判断是否是微信打开,ajax获取数据
  6. C++ reverse memcpy
  7. java小白会有那些工作_Java小白找工作与学习的第四天
  8. 如何在mfc主对话框中再显示子对话框_win10扩展显示器设置方法
  9. CSS3 Perspective
  10. c语言 单词长度统计,编写一个程序,打印输入中单词长度的直方图