Python编程 深入浅出递归

简介: 递归(Recursion)是一种解决问题的方法,其精髓在于将问题分解为规模更小的相同问题,持续分解,直到问题规模小到可以用非常简单直接的方式来解决。



一、初识递归


递归(Recursion)是一种解决问题的方法,其精髓在于将问题分解为规模更小的相同问题,持续分解,直到问题规模小到可以用非常简单直接的方式来解决。递归的问题分解方式非常独特,其算法方面的明显特征就是:在算法流程中调用自身。


递归为我们提供了一种对复杂问题的优雅解决方案,精妙的递归算法常会出奇简单,令人赞叹。


给定一个列表,返回所有数的和,列表中数字的个数不定,需要一个循环和一个累加变量来迭代求和,那现在既不能用 for 循坏,也不能用 while 循环,我们可以用递归的方法来解决问题!


思路:


  • 数列求和问题首先具备了基本结束条件:当列表长度为 1 的时候,直接输出所包含的唯一数。
  • 数列求和处理的数据对象是一个列表,而基本结束条件是长度为 1 的列表,那递归算法就要改变列表并向长度为 1 的状态演进,代码实现时具体做法是将列表长度减少1。
  • 调用自身:实际上可以理解为"问题分解成了规模更小的相同问题",在数列求和算法中就是"更短数列的求和问题"。


递归实现数列求和如下:


defsum_n(lst):
returnlst[0] iflen(lst) <=1elselst[0] +sum_n(lst[1:])
print(sum_n([1, 3, 5, 7, 9]))


递归算法三定律:


  • 递归算法必须要有结束条件(最小规模问题的直接解决)
  • 递归算法必须能改变状态向基本结束条件演进(减小问题规模)
  • 递归算法必须调用自身(解决减小了规模的相同问题)


递归调用的实现:


  • 当一个函数被调用的时候,系统会把调用时的现场数据压入到系统调用栈。每次调用,压入栈的现场数据称为栈帧,当函数返回时,要从调用栈的栈顶取得返回地址,恢复现场,弹出栈帧,按地址返回。
  • 在调试递归算法程序的时候经常会碰到这样的错误:RecursionError: maximum recursion depth exceeded in comparison,原因递归的层数太多,但系统调用栈容量是有限的。



爆栈是非常危险的操作,在实际开发写递归算法时应尽力避免。Python内置的 sys 模块可以获取和调整最大递归深度,操作如下:



二、进制转换


  • 十进制有十个不同符号:dec_str=“0123456789”,比 10 小的整数,转换成十进制,直接查表就可以得到:dec_str[n],把比 10 大的整数,拆成一系列比十小的整数,逐个查表,比如七百六十九,拆成七、六、九,查表就可以得到769。
  • 所以,在递归三定律里,我们找到了 “基本结束条件”,就是小于 10 的整数拆解整数的过程就是向“基本结束条件”演进的过程”。
  • 我们用整数除,和求余数两个计算来将整数一步步拆开,除以 “进制基base”(//base) 对 “进制基” 求余数(%base)


递归实现如下:


defdec_conversion_n(n, base):
str_list="0123456789ABCDEF"ifn<base:
returnstr_list[n]  # 到了最小规模  查表else:   # 减小规模  调用自身returndec_conversion_n(n//base, base) +str_list[n%base]
print(dec_conversion_n(233, 8))


结果如下:


还可以写得更优雅一些:


defdec_conversion_n(n, base):
str_list="0123456789ABCDEF"returnstr_list[n] ifn<baseelsedec_conversion_n(n//base, base) +str_list[n%base]
print(dec_conversion_n(233, 8))


余数总小于"进制基base",整数商小于进制基时,达到递归结束条件,可直接进行查表转换,整数商成为 “更小规模” 问题,通过递归调用自身解决。


三、递归可视化


通过可视化来展现递归调用。


importturtlet=turtle.Turtle()
defdraw_spiral(line_len):
ifline_len>0:
t.forward(line_len)
t.right(90)
draw_spiral(line_len-5)
draw_spiral(160)
turtle.done()


用分形树更形象地展现递归调用。分形是在不同尺度上都具有相似性的事物,分形树特征:子图结构与自身相似,很容易想到递归。


python中的 turtle 的使用,可以很方便地画出分形树,画分形树的思想也可以用到二叉树的遍历中,实现如下:


defdraw_tree(branch_len):
ifbranch_len>5:
t.forward(branch_len)
t.right(20)
draw_tree(branch_len-15)
t.left(40)
draw_tree(branch_len-15)
t.right(20)
t.backward(branch_len)
print(":".join(["CSDN叶庭云", "https://yetingyun.blog.csdn.net/"]))
t=turtle.Turtle()
t.left(90)
t.penup()
t.backward(100)
t.pendown()
t.pencolor('red')
t.pensize(2)
draw_tree(75)
t.hideturtle()
turtle.done()


效果如下:


把树分解为三个部分:树干、左边的小树、右边的小树分解后,正好符合递归的定义:对自身的调用。



谢尔宾斯基三角形(英语:Sierpinski triangle)也是一种分形,由波兰数学家谢尔宾斯基在 1915 年提出,它是自相似集的例子。根据自相似特性,谢尔宾斯基三角形是由 3 个尺寸减半的谢尔宾斯基三角形按照品字形拼叠而成,由于我们无法真正做出谢尔宾斯基三角形(degree趋于无穷),只能做 degree 有限的近似图形。


在 degree 有限的情况下,degree=n的三角形,是由 3 个 degree=n-1 的三角形,按照品字形拼叠而成。同时,这 3 个 degree=n-1 的三角形边长均为degree=n的三角形的一半(规模减小)。当degree=0,则就是一个等边三角形,这是递归基本结束条件。作图如下:


# -*- coding: UTF-8 -*-"""@Author   :叶庭云@公众号    :AI庭云君@CSDN     :https://yetingyun.blog.csdn.net/"""importturtledefdrawTriangle(points, color, my_turtle):   # 绘制等边三角形my_turtle.fillcolor(color)
my_turtle.up()
my_turtle.goto(points[0][0], points[0][1])
my_turtle.down()
my_turtle.begin_fill()
my_turtle.goto(points[1][0], points[1][1])
my_turtle.goto(points[2][0], points[2][1])
my_turtle.goto(points[0][0], points[0][1])
my_turtle.end_fill()
defgetMid(p1, p2):       # 取两个点的中心return (p1[0] +p2[0]) /2, (p1[1] +p2[1]) /2defsierpinski(points, degree, my_turtle):
colormap= ['blue', 'red', 'green', 'white',
'yellow', 'violet', 'orange']
drawTriangle(points, colormap[degree], my_turtle)
ifdegree>0:
sierpinski([points[0],
getMid(points[0], points[1]),
getMid(points[0], points[2])],
degree-1, my_turtle)
sierpinski([points[1],
getMid(points[0], points[1]),
getMid(points[1], points[2])],
degree-1, my_turtle)
sierpinski([points[2],
getMid(points[2], points[1]),
getMid(points[0], points[2])],
degree-1, my_turtle)
defmain():
print(":".join(["CSDN叶庭云", "https://yetingyun.blog.csdn.net/"]))
myTurtle=turtle.Turtle()
myWin=turtle.Screen()
myPoints= [[-100, -50], [0, 100], [100, -50]]   # 外轮廓三个顶点sierpinski(myPoints, 4, myTurtle)   # 画degree=5的三角形myWin.exitonclick()
main()


效果如下:


四、汉诺塔问题求解


问题来源:汉诺塔来源于印度传说的一个故事,上帝创造世界时作了三根金刚石柱子,在一根柱子上从上往下从小到大顺序摞着 64 片黄金圆盘。上帝命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一回只能移动一个圆盘,只能移动在最顶端的圆盘。有预言说,这件事完成时宇宙会在一瞬间闪电式毁灭。也有人相信婆罗门至今仍在一刻不停地搬动着圆盘。恩,当然这个传说并不可信,如今汉诺塔更多的是作为一个玩具存在。


推荐一个可以在线玩汉诺塔小游戏的网站:

http://www.htmleaf.com/Demo/201508272485.html


移 3 个盘子演示如下:


思路:


  • 将盘片塔从开始柱,经由中间柱,移动到目标柱:首先将上层N-1个盘片的盘片塔,从开始柱,经由目标柱,移动到中间柱;然后将第N个(最大的)盘片,从开始柱,移动到目标柱;
  • 最后将放置在中间柱的 N-1 个盘片的盘片塔,经由开始柱,移动到目标柱。基本结束条件,也就是最小规模问题变为:1个盘片的移动问题


Python代码递归实现如下:


defmove_tower(height, start_pole, mid_pole, target_pole):
ifheight>=1:
# 开始柱  目标柱  中间柱move_tower(height-1, start_pole, target_pole, mid_pole)
# 记录移动move_disk(height, start_pole, target_pole)
# 中间柱  开始柱  目标柱move_tower(height-1, mid_pole, start_pole, target_pole)
defmove_disk(disk, start_pole, target_pole):
print(f"将 {disk} 号盘子从 {start_pole}号柱 移到 {target_pole}号柱")
print(":".join(["CSDN叶庭云", "https://yetingyun.blog.csdn.net/"]))
move_tower(3, "1", "2", "3")    # 2^n - 1次print("Complete!")


结果如下:


和动图里我们玩游戏的操作步骤一致!


五、总结


递归是解决某些具有自相似性的复杂问题的有效技术


递归算法三定律:


  • 递归算法必须要有结束条件(最小规模问题的直接解决)
  • 递归算法必须能改变状态向基本结束条件演进(减小问题规模)
  • 递归算法必须调用自身(解决减小了规模的相同问题)


注意:


  • 某些情况下,递归可以代替迭代循环,递归算法通常能够跟问题的表达自然契合。
  • 递归不总是最合适的算法,有时候递归算法会引发巨量的重复计算,"记忆化/函数值缓存"可以通过附加存储空间记录中间计算结果来有效减少重复计算。
  • 如果一个问题最优解包括规模更小相同问题的最优解,这时我们可以用动态规划来解决。
目录
相关文章
|
13天前
|
机器学习/深度学习 存储 设计模式
Python 高级编程与实战:深入理解性能优化与调试技巧
本文深入探讨了Python的性能优化与调试技巧,涵盖profiling、caching、Cython等优化工具,以及pdb、logging、assert等调试方法。通过实战项目,如优化斐波那契数列计算和调试Web应用,帮助读者掌握这些技术,提升编程效率。附有进一步学习资源,助力读者深入学习。
|
13天前
|
机器学习/深度学习 数据可视化 TensorFlow
Python 高级编程与实战:深入理解数据科学与机器学习
本文深入探讨了Python在数据科学与机器学习中的应用,介绍了pandas、numpy、matplotlib等数据科学工具,以及scikit-learn、tensorflow、keras等机器学习库。通过实战项目,如数据可视化和鸢尾花数据集分类,帮助读者掌握这些技术。最后提供了进一步学习资源,助力提升Python编程技能。
|
1天前
|
Python
[oeasy]python074_ai辅助编程_水果程序_fruits_apple_banana_加法_python之禅
本文回顾了从模块导入变量和函数的方法,并通过一个求和程序实例,讲解了Python中输入处理、类型转换及异常处理的应用。重点分析了“明了胜于晦涩”(Explicit is better than implicit)的Python之禅理念,强调代码应清晰明确。最后总结了加法运算程序的实现过程,并预告后续内容将深入探讨变量类型的隐式与显式问题。附有相关资源链接供进一步学习。
15 4
|
13天前
|
设计模式 机器学习/深度学习 前端开发
Python 高级编程与实战:深入理解设计模式与软件架构
本文深入探讨了Python中的设计模式与软件架构,涵盖单例、工厂、观察者模式及MVC、微服务架构,并通过实战项目如插件系统和Web应用帮助读者掌握这些技术。文章提供了代码示例,便于理解和实践。最后推荐了进一步学习的资源,助力提升Python编程技能。
|
4天前
|
Java API Docker
在线编程实现!如何在Java后端通过DockerClient操作Docker生成python环境
以上内容是一个简单的实现在Java后端中通过DockerClient操作Docker生成python环境并执行代码,最后销毁的案例全过程,也是实现一个简单的在线编程后端API的完整流程,你可以在此基础上添加额外的辅助功能,比如上传文件、编辑文件、查阅文件、自定义安装等功能。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
在线编程实现!如何在Java后端通过DockerClient操作Docker生成python环境
|
12天前
|
机器学习/深度学习 设计模式 API
Python 高级编程与实战:构建 RESTful API
本文深入探讨了使用 Python 构建 RESTful API 的方法,涵盖 Flask、Django REST Framework 和 FastAPI 三个主流框架。通过实战项目示例,详细讲解了如何处理 GET、POST 请求,并返回相应数据。学习这些技术将帮助你掌握构建高效、可靠的 Web API。
|
12天前
|
机器学习/深度学习 设计模式 测试技术
Python 高级编程与实战:构建自动化测试框架
本文深入探讨了Python中的自动化测试框架,包括unittest、pytest和nose2,并通过实战项目帮助读者掌握这些技术。文中详细介绍了各框架的基本用法和示例代码,助力开发者快速验证代码正确性,减少手动测试工作量。学习资源推荐包括Python官方文档及Real Python等网站。
|
15天前
|
机器学习/深度学习 分布式计算 API
Python 高级编程与实战:深入理解并发编程与分布式系统
在前几篇文章中,我们探讨了 Python 的基础语法、面向对象编程、函数式编程、元编程、性能优化、调试技巧、数据科学、机器学习、Web 开发、API 设计、网络编程和异步IO。本文将深入探讨 Python 在并发编程和分布式系统中的应用,并通过实战项目帮助你掌握这些技术。
|
13天前
|
机器学习/深度学习 设计模式 API
Python 高级编程与实战:构建微服务架构
本文深入探讨了 Python 中的微服务架构,介绍了 Flask、FastAPI 和 Nameko 三个常用框架,并通过实战项目帮助读者掌握这些技术。每个框架都提供了构建微服务的示例代码,包括简单的 API 接口实现。通过学习本文,读者将能够使用 Python 构建高效、独立的微服务。
|
13天前
|
消息中间件 分布式计算 并行计算
Python 高级编程与实战:构建分布式系统
本文深入探讨了 Python 中的分布式系统,介绍了 ZeroMQ、Celery 和 Dask 等工具的使用方法,并通过实战项目帮助读者掌握这些技术。ZeroMQ 是高性能异步消息库,支持多种通信模式;Celery 是分布式任务队列,支持异步任务执行;Dask 是并行计算库,适用于大规模数据处理。文章结合具体代码示例,帮助读者理解如何使用这些工具构建分布式系统。

热门文章

最新文章