目录
递归Recursion
什么是递归
- 递归(recursion)是一种解决问题的方法,其精髓在于将问题分解为规模更小的相同问题,持续分解直到问题规模小到可以用非常简单直接的方式来解决。
- 递归的问题分解方式非常独特,其算法方面的明显特征就是在算法流程中调用自身。
递归三定律:
- 递归算法必须有一个基本结束条件(最小规模问题的直接解决)
- 递归算法必须能改变状态向基本结束条件演进(减小问题规模)
- 递归算法必须调用自身(解决减小了规模的相同问题)
递归“三定律”:数列求和问题
- 数列求和问题首先具备了基本结束条件:当列表长度为1的时候,直接输出所包含的唯一数
- 数列求和处理的数据对象是一个列表,而基本结束条件是长度为1的列表,那递归算法就要改变列表并向长度为1的状态演进我们看到其具体做法是将列表长度减少1。
- 调用自身是递归算法中最难理解的部分,实际上我们理解为“问题分解成了规模更小的相同问题”就可以了在数列求和算法中就是“更短数列的求和问题”。
# 递归版数列求和:
def listsum(numList):
if len(numList) == 1:
return numList[0]
else:
return numList[0] + listsum(numList[1:])
print(listsum([1,3,5,7,9]))
递归的应用:任意进制转换
- 假设是要转换为十进制,比十小的整数转换为十进制,直接查表就可以得到,比十大的整数就想办法拆成一系列比十小的整数,逐个查表。
- 所以递归基本结束条件就是小于十的整数,拆解整数的过程就是向基本结束条件演进的过程。
- 拆解过程分为两步:1. 除以“进制基base” (// base);2. 对“进制基”求余数(% base)。
- 此时问题就分解为:1. 余数总小于“进制基base”,是基本结束条件,可直接查表转换;2. 整数商成为更小规模问题,通过递归调用自身解决。
# 递归版整数转换为任意进制:
def toStr(n,base): # n为整数base为任意进制
convertString = "0123456789ABCDEF"
# print(convertString[5])
if n < base:
return convertString[n] # 最小规模
else:
return toStr(n//base,base) + convertString[n%base] # 整除减小规模,调用自身
print(toStr(1453,16))
递归调用的实现:递归深度的限制
- 当一个函数被调用的时候,系统会把调用时的现场数据压入到系统调用栈。
- 每次调用,压入栈的现场数据成为栈帧,当函数返回时,要从调用栈的栈顶取得返回地址,恢复现场,弹出栈帧,按地址返回。
- 在Python内置的sys模块可以获取和调整最大递归深度(不然可能会出现错误:RecursionError ,因为容量有限或者未设置基本结束条件导致无限递归)
import sys
sys.getrecursionlimit() # 返回1000
sys.setrecursionlimit(3000)
sys.getrecursionlimit() # 返回3000
递归的可视化:分形树
- Python的海龟做图系统turtle module是Python内置的,随时可用,以LOGO语言的创意为基础,其意象为模拟海龟在沙滩上爬行而留下的足迹。
-
爬行:forward(n); backward(n)
-
转向:left(a); right(a)
-
抬笔放笔:penup(); pendown()
-
笔属性:pensize(s); pencolor©
import turtle
# 正方形:
t = turtle.Turtle()
for i in range(4):
t.towards(100)
t.right(90)
turtle.done()
# 五角星:
t = turtle.Turtle()
t.pencolor("red")
t.pensize(3)
for i in range(5):
t.towards(100)
t.right(144)
t.hideturtle()
turtle.done()
# 螺旋线:
t = turtle.Turtle()
def drawSpiral(t,lineLen):
if lineLen > 0:
t.forward(lineLen)
t.right(90)
drawSpiral(t,lineLen-5)
drawSpiral(t,200)
turtle.done()
螺旋线
- 分形树:自相似递归图形
- 分形Fractal,是1975年由Mandelbrot开创的新学科。“一个粗糙或零碎的几何形状,可以分成数个部分,且每一部分都(至少近似地)是整体缩小后的形状”,即具有自相似的性质。
- 分形是在不同尺度上都具有相似性的事物。
import turtle
def tree(branch_len):
if branch_len > 5: # 树干太短不画,即递归结束条件
t.forward(branch_len) # 画树干
t.right(20) # 向右倾斜
tree(branch_len - 5) # 递归调用,画右边的小树
t.left(40) # 向左回40°,向左倾斜
tree(branch_len - 5) # 递归调用,画左边的小树
t.right(20) # 向右回20°,即回正
t.backward(branch_len) # 海归退回原位置
t = turtle.Turtle()
t.left(90)
t.penup()
t.backward(100)
t.pendown()
t.pencolor('green')
t.pensize(2)
tree(75) # 画树干长度为75的二叉树
t.hideturtle()
turtle.done()
分形树
递归的可视化:谢尔宾斯基三角形
- 分形构造,平面称谢尔宾斯基三角形,立体称谢尔宾斯基金字塔。实际上,真正的谢尔宾斯基三角形是完全不可见的,其面积为0,但周长无穷,是介于一维和二维之间的分数维(约1.585维)构造。
- 根据自相似特性,谢尔宾斯基三角形是由3个尺寸减半的谢尔宾斯基三角形按照品字形拼叠而成。由于我们无法真正做出谢尔宾斯基三角形(degree —> ∞),只能做degree有限的近似图形。
- 在degree有限的情况下,degree=n的三角形,是由3个degree=n-1的三角形按照品字形拼叠而成。同时,这3个degree=n-1的三角形边长均为degree=n的三角形的一半(规模减小)。当degree=0,则就是一个等边三角形,这是递归基本结束条件。
import turtle
def sierpinski(degree,points):
colormap = ['blue','red','green','yellow','orange','pink']
# 绘制等边三角形
drawTriangle(points,colormap[degree])
# 最小规模,0直接退出
if degree > 0:
# 减小规模,getMid边长减半,调用自身,左上右的次序
sierpinski(degree-1,{
'left':points['left'],
'top':getMid(points['left'],points['top']),
'right':getMid(points['left'],points['right'])})
sierpinski(degree-1,{
'left':getMid(points['left'],points['top']),
'top':points['top'],
'right':getMid(points['top'],points['right'])})
sierpinski(degree-1,{
'left':getMid(points['left'],points['right'])
'top':getMid(points['right'],points['top'])
'right':points['right']})
def drawTriangle(points,color):
t.fillcolor(color)
t.penup()
t.goto(points['top'])
t.pendown()
t.begin_fill()
t.goto(points['left'])
t.goto(points['right'])
t.goto(points['top'])
t.end_fill()
def getMid(p1,p2):
return((p1[0]+p2[0])/2,(p1