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

文章目录

    • 递归概念
    • 递归要素
    • 递归与迭代的区别
    • 示例一:阶乘
    • 示例二:斐波那契数列
    • 示例三:汉诺塔问题
    • 尾递归
    • 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)nfact(n1)

  • 递归必须必须要向着递归出口靠近,例如每次递归调用都会 n−1n-1n1,向着递归出口 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×...×(n1)×n,以递归方式定义:n!=(n−1)!×nn!=(n-1)!×nn!=(n1)!×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(n1)+F(n2)n3nN

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片黄金圆盘移动完毕之日,就是世界毁灭之时。

01汉诺塔

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

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

02汉诺塔

递归代码实现:

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
未经授权,禁止转载!恶意转载,后果自负!尊重原创,远离剽窃!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/437663.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【转】Postman系列五:Postman中电商网站cookie、token检验与参数传递实战

一&#xff1a;Postman中电商网站cookie实战 Postman接口请求使用cookie两种方式&#xff1a; 1.直接在header&#xff08;头域&#xff09;中添加cookie&#xff0c;适用于已知请求cookie头域的情况 2.使用Postman的cookie管理机制&#xff0c;即可以手动添加&#xff0c;同时…

Python 数据结构之栈的实现

文章目录栈的概念栈的特点栈的操作Python 实现栈栈的简单应用&#xff1a;括号匹配问题栈的简单应用&#xff1a;倒序输出一组元素栈的概念 栈&#xff08;stack&#xff09;又名堆栈&#xff0c;栈是一种线性数据结构&#xff0c;用先进后出或者是后进先出的方式存储数据&…

CSDN 2020 博客之星实时数据排名(Python 爬虫 + PyEcharts)

CSDN 2020 博客之星实时数据排名&#xff1a;csdn.itrhx.com CSDN 一年一度的博客之星评选开始了&#xff0c;官网地址&#xff1a;https://bss.csdn.net/m/topic/blog_star2020 &#xff0c;由于官网是按照随机编号排序的&#xff0c;没有按照票数多少排序&#xff0c;为了方便…

【转】注册Azure AD 应用程序

作者&#xff1a;陈希章 发表于2017年3月22日 在此前的文章中&#xff0c;我给大家介绍了分别用Graph 浏览器以及第三方工具&#xff08;POSTMAN&#xff09;快速体验Microsoft Graph的功能&#xff0c;其中有一个重要的环节就是&#xff0c;开发人员需要访问Microsoft Graph的…

Python + GitHub Actions 实现 CSDN 自动签到与抽奖(非 selenium 版本)

文章目录【1x00】技术栈【2x00】代码实现签到与抽奖【3x00】签到结果通知【03x01】Server 酱【03x02】企业微信【03x03】钉钉【4x00】自动签到【5x00】完整代码【6x00】如何使用【06x01】方法一&#xff1a;直接 Fork 代码&#xff08;推荐&#xff09;【06x01】方法二&#xf…

Spark安装配置

Scala基础语法学习的差不多了&#xff0c;先把spark安装上 首先官网下载解压安装后 进入到conf目录下修改文件名 修改spark-env.sh&#xff08;配置jdk路径&#xff09; export JAVA_HOME/usr/local/java/jdk1.8.0_221修改slaves&#xff08;添加子节点名&#xff09; chun…

【转】注册Azure AD 2.0 应用程序

作者&#xff1a;陈希章 发表于 2017年3月22日 上一篇 介绍了Microsoft Graph应用程序的一些概念&#xff0c;以及目前还比较普遍的Azure AD 1.0应用程序的注册方式。但正如我多次提到的那样&#xff0c;虽然目前功能还在不断完善&#xff0c;但Azure AD 2.0会逐渐成为主流&…

Python 采集 Facebook 评论插件、留言外挂程序

实现时间&#xff1a;2021-05-30实现难度&#xff1a;★★★☆☆☆实现目标&#xff1a;采集 Facebook 评论插件、留言外挂程序的所有评论。完整代码&#xff1a;https://github.com/TRHX/Python3-Spider-Practice/tree/master/CommentPlugin/facebook-comments其他爬虫实战代码…

写第一个spark程序(wordcount)

首先启动集群与spark 其次把spark目录下的README.md上传到hdfs 进入spark下的bin目录&#xff0c;运行spark-shell ./spark-shell运行 val textFile sc.textFile("hdfs://chun1:9000/spark/README.md")val wordCounts textFile.flatMap(line>line.split("…

【中国版Office 365 应用程序注册】

中国版Office 365是由世纪互联进行运营的一个云服务&#xff0c;单纯从技术角度来看的话&#xff0c;它基本保持了与国际版的同步。但是由于两个版本本质上是完全独立的&#xff0c;其中最关键的就是账号系统是分开的&#xff0c;所以从使用角度来看&#xff0c;不管是直接用户…

Python 中如何解决 asyncio 文件描述符最大数量限制问题

文章目录问题复现问题分析事件循环 EventLoopI/O 多路复用select 的缺点解决方法1.更换事件循环选择器2.限制并发量3.修改最大文件描述符限制WindowsLinux总结WindowsLinux问题复现 Windows 平台下&#xff0c;Python 版本 3.5&#xff0c;使用异步框架 asyncio&#xff0c;有…

【转】掀起Azure AD的盖头来——深入理解Microsoft Graph应用程序和服务权限声明

引子 这是一篇计划外的文章。我们都知道要进行Microsoft Graph的开发的话&#xff0c;需要进行应用程序注册。这个在此前我已经有专门的文章写过了。但这里存在一个小的问题&#xff1a;国内版的Office 365在申请好之后&#xff0c;并没有像国际版那样&#xff0c;有一个对应的…

Python3 学习系列 丨 博客目录索引

整个博客有关 Python 学习目录索引&#xff0c;方便快捷定位查询基础学习篇 Python3 基础学习笔记 C01【变量和简单数据类型】Python3 基础学习笔记 C02【列表】Python3 基础学习笔记 C03【操作列表】Python3 基础学习笔记 C04【if 语句】Python3 基础学习笔记 C05【字典】Pyt…

【转】日邮物流:实现智慧物流,这个云上对了!

和阳光、空气、水、网络一样&#xff0c;「物流」早已成为当代企业、个人赖以生存的必要条件。2020第一季度全球物流受疫情影响面临挑战&#xff0c;业内普遍预计全球物流及供应链将重新优化布局。借此时机&#xff0c;物流业纷纷将目光投向“数字化智慧物流”方向&#xff0c;…

Python 实现十大经典排序算法

目录排序算法分类一、冒泡排序&#xff08;Bubble Sort&#xff09;1、原理2、步骤3、动画演示4、代码实现5、具体示例二、选择排序&#xff08;Selection Sort&#xff09;1、原理2、步骤3、动画演示4、代码实现5、具体示例三、插入排序&#xff08;Insertion Sort&#xff09…

【转】Microsoft Graph 桌面应用程序

桌面应用程序&#xff0c;在我这篇文章的语境中&#xff0c;我是特指在Windows桌面上面直接运行的.NET应用程序&#xff0c;包括Console Application&#xff0c;WPF Application&#xff0c;Windows Forms Application, UWP Application&#xff0c;并且限于篇幅&#xff0c;我…

【转】Microsoft Graph Web应用程序极致开发体验

前言 这篇文章最早写于2017年5月2日&#xff0c;当时的想法是从最简单的方式来写如何在一个ASP.NET MVC应用程序中集成Microsoft Graph&#xff0c;但实际上还真不是那么简单&#xff0c;至少我是不满意的&#xff0c;加上这一两周都比较忙&#xff0c;所以这一篇就一直搁置。…

Spark(idea)操作mysql进行查询和插入 (代码+理解)

首先在maven中加入配置 <!--mysql--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.27</version></dependency>然后在idea配置数据库 1&#xff09; 查询 //1.查询数…

【转】在无人值守程序(服务)中调用Microsoft Graph

什么是无人值守程序&#xff08;服务&#xff09; 我在此前用了几篇文章分别介绍了在桌面应用程序&#xff08;控制台&#xff09;&#xff0c;Web应用程序&#xff08;ASP.NET MVC&#xff09;&#xff0c;以及PowerSehll脚本中如何访问Microsoft Graph&#xff0c;今天这一篇…

【转】使用PowerApps快速构建基于主题的轻业务应用 —— 入门篇

前言 在上一篇文章 基于Office 365的随需应变业务应用平台 中我提到&#xff0c;随着随需应变的业务需要&#xff0c;以及技术的发展&#xff0c;业务应用的开发的模式也有了深刻的变化。基于微软的平台&#xff0c;有服务于主干业务应用的Dynamic 365 业务应用平台&#xff0…