【Python基础】15. 递归函数 Recursion Function

什么是递归函数

一种计算过程,如果其中每一步都要用到前一步或前几步的结果,称为递归的。用递归过程定义的函数,称为递归函数,例如连加、连乘及阶乘等。凡是递归的函数,都是可计算的,即能行的。

  • 递归就是一个函数在它的函数体内调用它自身。

编程语言中的对递归定义:

  • 编程语言中,函数Func(Type a,……)直接或间接调用函数本身,则该函数称为递归函数。递归函数不能定义为内联函数。

数学中对递归的定义:

  • 在数学上,关于递归函数的定义如下:对于某一函数f(x),其定义域是集合A,那么若对于A集合中的某一个值X0,其函数值f(x0)由f(f(x0))决定,那么就称f(x)为递归函数。

递归的条件:

  • 一个含直接或间接调用本函数语句的函数被称之为递归函数,在上面的例子中能够看出,它必须满足以下两个条件:
    • 1)执行递归函数将反复调用其自身,每调用一次就进入新的一层。
    • 2)必须有结束条件,即必须有一个终止处理或计算的准则。

递归函数的应用

应用一: 计算阶乘(factorial)

  • 定义:一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且0的阶乘为1。自然数n的阶乘写作n!

  • 任何大于等于1 的自然数n 阶乘表示方法:n! = 1*2*3* ...*(n-1)*n,或,n! = n *(n-1)!

  • 阶乘的规律:

    1! = 1
    2! = 2 × 1 = 2 × 1!
    3! = 3 × 2 × 1 = 3 × 2!
    4! = 4 × 3 × 2 × 1 = 4 × 3!
    ...
    n! = n × (n-1)!
    
  • 用递归函数来计算阶乘: 通过用户输入数字(n)计算阶乘

    # 获取用户输入的数字
    num = int(input("请输入一个数字: "))
    factorial = 1
    
    # 查看数字是负数,0 或 正数
    if num < 0:
        print("抱歉,负数没有阶乘")
    elif num == 0:
        print("0 的阶乘为 1")
    else:
         for i in range(1,num + 1):
            factorial = factorial*I
    
    print("%d 的阶乘为 %d" %(num,factorial))
    
  • 上述代码运行结果如下:

    请输入一个数字: 3   #输入3,求3的阶乘. 3! = 3*2*1 =6
    3 的阶乘为 6
    

-上述递归函数的调用过程:

递归函数阶乘3的调用.png
  • 在Python中,还可以使用循环来实现阶乘的计算:

    • 使用while循环实现计算3的阶乘
    n=4      #求4的阶乘
    result=1
    I=1
    while i<=4:
      result=result*I
      I+=1
    
    print(result)
    

从上面两中方法的对比可以看出,递归函数的作用和循环的方法效果一样,即递归函数本质上是一个方法的循环调用,注意:有可能会出现死循环。因此,使用递归函数时,一定要定义递归的边界(即什么时候退出循环)。

应用二: 计算斐波那契数列 (Fibonacci sequence)

  • 斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家列昂纳多·斐波那契以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:
    • 1,1,2,3,5,8,13,21,34,55,89,144……
    • 从3三个数开始,后一个数等于前面两个数的和
    • 在数学上,斐波纳契数列以如下被以递归的方法定义:F0=0,F1=1,Fn=F(n-1)+F(n-2)(n>=2,n∈N*)
斐波那契数列_兔兔的繁殖.jpg
  • 用递归函数来实现获取斐波拉契数列中第n个数字的值:

    #  计算斐波那契数列第n位的值
    def fab(n):
        if n > 2:
            return fab(n-1) + fab(n-2)
        else:
            return 1
    
    #  打印斐波那契数列
    def printfablist(n):
        for i in range(1, n+1):
            print(fab(i),end = ' ')
    
    # 传参调用
    printfablist(int(input('请输入要输出多少项:')))
    
  • 上述代码运行结果如下:

    请输入要输出多少项:4    #键入4,求斐波那契数列前四项
    1 1 2 3   # 得到斐波那契数列前四项
    
  • 同样的,除了递归函数外,还可以使用while循环来实现斐波那契数列:

    # 获取用户输入数据
    num_n = int(input("请输入你需要几项:"))
    
    # 第一和第二项
    n1 = 1
    n2 = 1
    count = 2
    
    # 判断输入的值是否合法
    if num_n <= 0:
        print("请输入一个正整数。")
    elif num_n == 1:
        print("斐波那契数列:")
        print(n1)
    else:
        print("斐波那契数列:")
        print(n1,",",n2,end=" , ")
        while count < num_n:
            nth = n1 + n2
            print(nth,end=" , ")
            # 更新值
            n1 = n2
            n2 = nth
            count += 1
    
  • 上述代码运行结果如下:

    请输入你需要几项:4  #键入4,求斐波那契数列前四项
    斐波那契数列:
    1 , 1 , 2 , 3 ,     # 得到斐波那契数列前四项
    

从上面两中方法的对比可以看出,递归函数的作用和循环的方法效果一样,即递归函数本质上是一个方法的循环调用,注意:有可能会出现死循环。因此,使用递归函数时,一定要定义递归的边界(即什么时候退出循环)。

以上两个案例是递归函数的经典案例,需要记住其使用方法。==循环能干的事,递归都能干;递归能干的循环不一定能干==

递归函数特点

递归:自我调用且有完成状态。

  1. 每一级函数调用时都有自己的变量,但是函数代码并不会得到复制,如计算5的阶乘时每递推一次变量都不同;

  2. 每次调用都会有一次返回,如计算5的阶乘时每递推一次都返回进行下一次;

  3. 递归函数中,位于递归调用前的语句和各级被调用函数具有相同的执行顺序;

  4. 递归函数中,位于递归调用后的语句的执行顺序和各个被调用函数的顺序相反;

  5. 递归函数中必须有终止语句。

递归函数的缺点: 过深的调用会导致栈溢出

递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。

使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。

  • 使用python写的递归程序如果递归太深, 那么极有可能因为超过系统默认的递归深度限制而出现
    • 例如使用递归计算阶乘时,传入参数值1000来调用函数factoria(1000),运行会报错:
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 File "<stdin>", line 4, in factoria
 ...
 File "<stdin>", line 4, in factoria
RuntimeError: maximum recursion depth exceeded
  • 解决上述报错问题的方法很简单, 人为将系统设定的递归深度设置为一个较大的值即可:

    import sys
    sys.setrecursionlimit(1000000) #括号中的值为递归深度
    

参考资源:

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容

  • 第八章 递归(recursion) 8.1 导语 因为一些指导者倾向于先教递归作为第一个主要的控制结构,本章会以另...
    geoeee阅读 1,400评论 0 5
  • 感谢社区中各位的大力支持,译者再次奉上一点点福利:阿里云产品券,享受所有官网优惠,并抽取幸运大奖:点击这里领取 在...
    HetfieldJoe阅读 1,801评论 0 14
  • 8月22日-----字符串相关 2-3 个性化消息: 将用户的姓名存到一个变量中,并向该用户显示一条消息。显示的消...
    future_d180阅读 959评论 0 1
  • 我的观点是:教育就像一颗种子成长为苍天大树所经历的过程和所需要的元素的总和。 这个过程中最核心的几个角色: 父母(...
    王五月阅读 716评论 0 0
  • 响叮当,响叮当,铃儿响叮当 “丁当,你怎么就只吹这一首曲子啊?” “玲儿,那是因为我就只会吹这一首曲子” “明...
    青春吐芳华阅读 871评论 0 0