【Python】第5章 递归

目录

5.1   递归的基本概念

5.2   先做一步再递归:上台阶问题

5.3   问题分解:汉诺塔问题

5.4   递归替代循环:N 皇后问题

5.5   递归绘制分形图案:绘制雪花曲线


5.1   递归的基本概念

        一个概念的定义中用到了这个概念本身,这就叫递归。例如,假定有个概念叫“堆乘”,用如下两句话定义“n的堆乘”(不妨记为“n#”),就是递归:

        (1)“n的堆乘”就是n乘以“(n - 1)的堆乘”;

        (2)“1的堆乘”是1。

        第1句中,解释“堆乘”这个词的时候用到了“堆乘”这个词,貌似没完没了的循环定义,让人搞不明白。如果没有第2句,那确实如此。有了第2句,n的堆乘到底是什么,就可以由1的堆乘是1,逐步递推出来:4#=4×3#、3#=3×2#、2#=2×1#、1#=1,倒推回去,可以得到4#=4×3×2×1。原来堆乘就是阶乘。第2句话使得面对“1的堆乘是什么”这样的问题时,不必再用让人搞不懂的“1的堆乘等于1乘以0的堆乘”来回答,而是直接得到答案1,因此第2句可以称为递归的“终止条件”。

        在程序设计中,一个函数自己调用了自己,就称为递归。其实函数调用自己,和调用别的函数并无本质区别,完全可以看作是调用了另一个同功能函数。调用自己的函数,称为递归函数。下面是一个求n阶乘(n>=1)的递归函数:

def F(n):          #函数返回n的阶乘
    if n == 1:     #终止条件
        return 1
    return n * F(n-1)
print(F(4))        #>>24
print(F(5))        #>>120

        递归函数是如何执行的,初学者往往难以理解。图5.1.1演示了F(4)的计算过程(从F(4)开始顺着箭头方向看)。

         算F(4)时,进入F函数,此刻n=4。要算F(4),就要先算F(3),于是再次进入F函数,此刻n=3。F(4)算是第一层函数调用,F(3)就是第二层。每一层调用的n的值不同,不会互相影响。将调用自己看作调用另一个同功能的函数,即可很自然理解这一点。整个执行过程还可以描述如下:

F(4)2->F(4)4->F(3)2->F(3)4->F(2)2->F(2)4->F(1)2->F(1)3:返回1->
F(2)4:返回2*1->F(3)4:返回3*2->F(4)4:返回4*6->函数执行结束

         上面的F(i)j表示在n=i那一层的函数调用中,执行第j行。最先执行的是F(4)2,表示在n=4的那一层函数调用中,先执行第2行“ifn==1:”。接下来执行F(4)4,第4行在执行的过程中进入下一层函数调用,下一层函数调用中n=3,所以F(4)4后面被执行的就是F(3)2,再接下来是F(3)4……执行到F(1)3后,函数开始逐层向上返回,先返回到F(2)4,把n=2时的第4行执行完毕,返回值是2*F(1),即1*1,返回到F(3)4,F(3)4执行完则返回值为3*F(2),即3*2=6,并且返回到F(4)4,F(4)4返回4*6,函数调用结束。

        由此可见,递归函数一定要有一个终止递归的条件,满足此条件时,函数就返回,不再调用自身。否则,递归就会没完没了地进行下去。无休止的递归会导致“栈溢出”而使得程序崩溃。有时程序中没有死循环,然而却总是不能结束,就要考虑是否发生了无限递归。

        上面F函数的终止条件,就是n==1。

        求斐波那契数列的第n项,也可以用递归的办法完成:

def Fib(n):                         #求斐波那契数列第n项
    if n == 1 or n == 2:
        return 1
    else:
        return Fib(n-1)+Fib(n-2)    #第n项等于第n-1项和第n-2项之和
print(Fib(6))                       #>>8

        但是这个递归的做法,存在大量重复计算,例如算fib(5)时会把fib(4)从头到尾算一遍,算fib(6)时又要把fib(4)从头到尾算一遍……因此其计算速度远远慢于前面的循环解法,用个人计算机来算,十万年未必能算出第100项,只能用来演示一下递归的思想。

        递归可以用来替代循环。假设有函数g(),下面的循环:

for i in range(4):
        g()

         可以用调用一个递归函数来替代:

defcall(f,n):#参数f是函数ifn>0:f()#调用f代表的函数call(f,n-1)call(g,4)

        程序设计语言中有递归,就可以不需要循环。有的语言,比如LISP,就是如此。有了循环,其实也可以不需要递归,只是不够方便。当然大多数程序设计语言都是同时支持递归和循环的。

        递归和循环可以互相替代这件事情有点深奥,非计算机专业的读者可以不必深究。

        默认情况下,Python递归函数最多只能执行大约1000层,就会导致栈溢出的RuntimeError。可以用sys.setrecursionlimit()函数来增加最大递归深度,请读者自行查阅相关介绍。但是,这个函数也不是非常好使,Python相比其他语言更容易因递归太深导致RE。

5.2   先做一步再递归:上台阶问题

5.3   问题分解:汉诺塔问题

5.4   递归替代循环:N 皇后问题

注:5.2,5.3,5.4 这三个利用递归解决的实际问题,详见【Python习题】感悟&易错点总结

5.5   递归绘制分形图案:绘制雪花曲线

        要进行绘图,可以使用Python自带的turtle库。turtle库中有许多函数支持绘图,用法是turtle.xxx(...),xxx是函数名。绘图是在一个窗口中进行的,用turtle.setup(x,y)可以创建一个宽x像素,高y像素的窗口,窗口会出现在屏幕中央。窗口是一个平面直角坐标系,窗口的中心位置是坐标系原点,即其坐标是(0,0)。这个坐标系有方向的概念,方向用角度来表示。正东方向是0度,正北方向是90度,正西方向是180度,正南方向是270度。当然也可以说正南方向是-90度,正西方向是−180度。

        不妨把turtle创建的窗口想象成一张纸。这张纸上有一支虚拟的笔。笔开始的位置是在(0,0),且笔是落在纸上的。当笔落在纸上移动时,就会画出线条。笔是有前进方向的。笔的初始方向是0度。turtle.fd(x)会使笔沿着前进方向移动x像素。turtle.left(x)会使得笔的方向左转x度,turtle.right(x)会使得笔的方向右转x度。

        下面要在窗口上绘制雪花曲线。雪花曲线也称为科赫曲线,其递归定义如下:

        (1)长为size,方向为x(x是角度)的0阶雪花曲线,是沿方向x绘制的一根长为size的线段。

        (2)长为size,方向为x的n阶雪花曲线,由以下四部分依次拼接组成。

        ①长为size/3,方向为x的n−1阶雪花曲线。

        ②长为size/3,方向为x+60的n−1阶雪花曲线。

        ③长为size/3,方向为x−60的n−1阶雪花曲线。

        ④长为size/3,方向为x的n−1阶雪花曲线。

        图5.5.1~图5.5.3是几个雪花曲线的示意图。

         绘制长度为600像素,方向为0度的3阶雪花曲线的程序如下:

import turtle        #画图要用这个turtle库
def snow(n,size):
#从笔的当前位置出发,在笔的当前方向画一个长度为size的n阶的雪花曲线
    if n == 0:    #0阶曲线
        turtle.fd(size)               #笔沿着当前方向前进size个像素
    else:
        for angle in [0,60,-120,60]:
            turtle.left(angle)        #笔左转angle度,用turtle.lt(angle)也可以
            snow(n-1,size/3)

turtle.setup(800,600)            #创建窗口
turtle.penup()                   #抬起笔,这样笔在移动时就不会在窗口上画线
turtle.goto(-300,0)              #将笔移动到(-300,0)位置
turtle.pendown()                 #放下笔
turtle.pensize(3)                #设置笔的粗度为3像素
snow(3,600)                      #绘制长度为600,阶为3的雪花曲线,方向为0度
turtle.done()                    #保持绘图窗口,无此则画完图窗口自动关闭

        程序运行结果如图5.5.4所示。

        第1行:本行的作用是将turtle库“引入”进来,这样后面的标识符“turtle”才有定义。

        函数snow(n,size)的含义是,从笔的当前位置出发,沿着笔的当前方向,画一条长为size的n阶雪花曲线。

        第5行:0阶雪花曲线就是一条长为size的线段,turtle.fd(size)的含义是,沿当前笔的方向前进size,画出一条长为size的线段。

        第7行到第9行:按照雪花曲线的递归定义,一条笔的当前方向上的长为size的n阶雪花曲线,应该由4段长为size/3的n-1阶雪花曲线连接而成。这个循环就依次画出这四段。若笔当前方向是x,则这四段的方向依次是x,x+60,x-60,x。可以看出,若n-1阶雪花曲线画完时笔的方向不变(和开始画时一样),那么n阶雪花曲线画完时笔的方向也不变。再加上0阶雪花曲线画完时笔的方向是不变的,由数学归纳法可知任何阶数的雪花曲线,画完时笔的方向都和开始画时一样。连画4段n-1阶雪花曲线,需要在画完一段后修改笔的方向,再画下一段。修改笔的方向,可以通过让笔左转某个角度来进行。调用turtle.left(d),即可以让笔的方向左转d度。因此要依次画这四段方向为x,x+60,x-60,x的n-1阶雪花曲线,就可以让笔先左转0度(等于没转)画第一段,再左转60度画第二段,再右转120度(即左转−120度)画第三段,然后再左转60度回到最初的方向x画第四段。

        有了绘制雪花曲线的函数snow后,就可以绘制一个完整的雪花,如图5.5.5所示。

        可以看出,该雪花由方向依次是0度、240度、120度的三段3阶雪花曲线构成。绘图函数如下:

def snowPiece():
    turtle.setup(800,800)
    turtle.speed(1000)              #设置绘画速度    
    turtle.penup()
    turtle.goto(-200,100)
    turtle.pendown()
    turtle.pensize(2)
    snow(3,400)                     #画0度雪花曲线
    turtle.right(120)               #右拐120度
    snow(3,400)                     #画-120度(即240度)雪花曲线
    turtle.right(120)snow(3,400)    #画120度(即-240度)雪花曲线
turtle.done()

        雪花曲线是一种分形图形。什么是分形图形,言传有点难。大概说来,一个图形,由和整体图形相似的n个局部构成,每个局部又是由n个更小的和整体图形相似的局部构成……这样的图形就是分形图形。当然这是个非常不精确的模糊定义,请读者自己意会。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值