2.递归

目录

什么是递归Recursion?

递归“三定律”

递归的应用

1.任意进制转换

2.递归可视化

1.分形树

2.谢尔宾斯基三角形

3.找零兑换(兑换最少个数硬币)

1.贪心策略Greedy Method

2.递归解法

递归调用的实现

Python中的递归深度限制

 


 



什么是递归Recursion?

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

递归“三定律”

1.必须有一个基本结束条件(最小规模问题的直接解决)

2.必须能改变状态向基本结束条件演进(减小问题规模)

3.必须调用自身(解决减小了规模相同问题)

 

递归的应用

1.任意进制转换

用最熟悉的十进制分析问题:十进制有十个不同符号:convString="0123456789",比十小的整数,转换成十进制,直接查表就可以了:convString[n];想办法把比十大的整数,拆成一系列比十小的整数,逐个查表,比如七百六十九,拆成七、六、九,查表可以得到769就可以了。

所以,在递归三定律里,找到了“基本结束条件”,就是小于十的整数,拆解整数的过程就是向“基本结束条件”演进的过程。

我们用整数除求余数两个计算来将整数一步步拆开,除以“进制基base”(//base) ; 对“进制基”求余数(%base)

问题就分解为:余数总小于“进制基base”,是“基本结束条件”,可直接进行查表转换;整数商成为“更小规模”问题,通过递归调用自身解决

def toStr(n,base):
    convertString="0123456789ABCDEF"
    if n<base:
        return convertString[n]#最小规模
    else:
        return toStr(n//base,base)+convertString[n%base]#减小规模,调用自身
print(toStr(1453,16))
##5AD

2.递归可视化

Python的海龟作图系统turtle module

1.分形树

自相似递归图形。分形是在不同尺度上都具有相似性的事物。

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

import turtle
def tree(branch_len):
    if branch_len>5:#树干太短不画,即递归结束条件
        t.forward(branch_len)#画树干
        t.right(20)#右倾斜20度
        tree(branch_len-15)#递归调用,画右边的小树,树干减15
        t.left(40)#向左回40度,即左倾斜20度
        tree(branch_len-15)#递归调用,画左边的小树,树干减15
        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()

2.谢尔宾斯基三角形

分形构造,平面称为谢尔宾斯基三角形,立体称为谢尔宾斯基金字塔。实际上,真正的谢尔宾斯基三角形是完全不可见的,其面积为0,但周长无穷,是介于一维和二维之间的分数维(约1.585维)构造。根据自相似性,谢尔宾斯基三角形是由3个尺寸减半的谢尔宾斯基三角形按照品字形拼叠而成。

import turtle

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[1]+p2[1])/2)
def sierpinski(degree,points):
    colormap=['blue','red','green','white','yellow','orange']
    drawTriangle(points,colormap[degree])
    if degree>0:#最小规模0直接退出
        #调用自身,左上右次序
        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['top'], points['right']),
                    'right': points['right']})
t=turtle.Turtle()
points={'left':(-200,-100),#外轮廓三个顶点
        'top':(0,200),
        'right':(200,-100)}
sierpinski(5,points)#画degree为5的三角形
turtle.done()

3.找零兑换(兑换最少个数硬币)

1.贪心策略Greedy Method

因为我们每次都试图解决问题的尽量大的部分对应到兑换硬币问题,就是每次以最多数量的最大面值硬币来迅速减少找零问题。该策略在美元或其他货币的硬币体系下表现尚好,但在有特殊面值的情况下就会变得复杂起来,如下,此时该策略失效。贪心策略是否有效依赖于具体的硬币体系。

硬币[1,5,10,25]: 63=25*2+10*1+1*3

硬币存在21时:63=21*3

2.递归解法

首先,确定基本结束条件:需找零的面值正好等于某种硬币

其次,减小问题的规模。对每种硬币尝试1次,例如美元硬币体系:找零减去1分后,求兑换硬币最少数量(递归调用自身);找零减去5分后,求兑换硬币最少数量;再分别减去10分,25分。上述4项选择最小的一个。

def recMc(coinValueList,change):#硬币体系,找零
    minCoins=change#最大能兑换出change个
    if change in coinValueList:#最小规模,直接返回1
        return 1
    else:
        for i in [c for c in coinValueList if c<=change]:
            #减小规模:每次减去一种硬币面值,挑选最小数量
            numCoins=1+recMc(coinValueList,change-i)
            if numCoins<minCoins:
                minCoins=numCoins
    return minCoins
print(recMc([1,5,10,25],63))
#6

递归解法虽然能够解决问题,但其最大的问题是:极其低效,重复计算太多了。

对上面的递归解法进行改进的关键在于消除重复计算:我们可以用一个表将计算过的中间结果保存起来,在计算之前查表看看是否已经计算过。这个算法的中间结果就是部分找零的最优解,在递归调用过程中已经得到的最优解被记录下来。改进后的解法极大地减少了递归调用的次数,运行结果很快得出。

def recDc(coinValueList,change,knownResults):#硬币体系,找零,存储中间结果的表
    minCoins=change#最大能兑换出change个
    if change in coinValueList:#最小规模,直接返回1
        knownResults[change]=1#记录最优解
        return 1
    elif knownResults[change]>0:
        return knownResults[change]#查表成功,直接返回最优解,不进行递归调用
    else:
        for i in [c for c in coinValueList if c<=change]:
            #减小规模:每次减去一种硬币面值,挑选最小数量
            numCoins=1+recDc(coinValueList,change-i,knownResults)
            if numCoins<minCoins:
                minCoins=numCoins
                knownResults[change]=minCoins
    return minCoins
print(recDc([1,5,10,25],63,[0]*64))
#6

 

递归调用的实现

当一个函数被调用时,系统会把调用时的现场数据压入到系统调用栈。每次调用,压入栈的现场数据称为栈帧。当函数返回时,要从调用栈的栈顶取得返回地址,恢复现场,弹出栈帧,按地址返回。

Python中的递归深度限制

在调试递归算法程序的时候经常会碰到这样的错误:RecursionError,递归的层数太多,系统调用栈容量有限。这时候要检查程序是否忘记设置基本结束条件,导致无限递归,或者向基本结束条件演进太慢,导致递归层数太多,调用栈溢出。

在Python内置的sys模块可以获取和调整最大递归深度

import sys
print(sys.getrecursionlimit())
sys.setrecursionlimit(3000)
print(sys.getrecursionlimit())

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值