递归函数

在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。

递归函数特性:

  1. 必须有一个明确的结束条件;
  2. 每次进入更深一层递归时,问题规模相比上次递归都应有所减少
  3. 相邻两次重复之间有紧密的联系,前一次要为后一次做准备(通常前一次的输出就作为后一次的输入)。
  4. 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出)

先举个简单的例子:计算1到100之间相加之和;通过循环和递归两种方式实现

# 循环方式
def sum_cycle(n): sum = 0 for i in range(1,n+1) : sum += i print(sum)# 递归方式
def sum_recu(n): if n>0: return n +sum_recu(n-1) else: return 0 sum_cycle(100)
sum = sum_recu(100) print(sum)

结果:

5050
5050

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

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

把上面的递归求和函数的参数改成10000就导致栈溢出!

RecursionError: maximum recursion depth exceeded in comparison

**解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。

一般递归

def normal_recursion(n):if n == 1:return 1else:return n + normal_recursion(n-1)

执行:

normal_recursion(5)
5 + normal_recursion(4)
5 + 4 + normal_recursion(3)
5 + 4 + 3 + normal_recursion(2)
5 + 4 + 3 + 2 + normal_recursion(1)
5 + 4 + 3 + 3
5 + 4 + 6
5 + 10
15

可以看到, 一般递归, 每一级递归都需要调用函数, 会创建新的栈,随着递归深度的增加, 创建的栈越来越多, 造成爆栈:boom:

尾递归(http://www.open-open.com/lib/view/open1480494663229.html

尾递归基于函数的尾调用, 每一级调用直接返回函数的返回值更新调用栈,而不用创建新的调用栈, 类似迭代的实现, 时间和空间上均优化了一般递归!

def tail_recursion(n, total=0):if n == 0:return totalelse:return tail_recursion(n-1, total+n)

执行:

tail_recursion(5)
tail_recursion(4, 5)
tail_recursion(3, 9)
tail_recursion(2, 12)
tail_recursion(1, 14)
tail_recursion(0, 15)
15

可以看到, 每一级递归的函数调用变成"线性"的形式.

深入理解尾递归

呃, 所以呢? 是不是感觉还不够过瘾... 谁说尾递归调用就不用创建新的栈呢?

还是让我们去底层一探究竟吧

int tail_recursion(int n, int total) {if (n == 0) {return total;}else {return tail_recursion(n-1, total+n);}
}int main(void) {int total = 0, n = 4;tail_recursion(n, total);return 0;
}

反汇编

  • $ gcc -S tail_recursion.c -o normal_recursion.S

  • $ gcc -S -O2 tail_recursion.c -o tail_recursion.S gcc开启尾递归优化

对比反汇编代码如下(AT&T语法)

可以看到, 开启尾递归优化前, 使用call调用函数, 创建了新的调用栈(LBB0_3);

而开启尾递归优化后, 就没有新的调用栈生成了, 而是直接pop

bp指向的 _tail_recursion 函数的地址(pushq %rbp)然后返回,

仍旧用的是同一个调用栈!

存在的问题

虽然尾递归优化很好, 但python 不支持尾递归,递归深度超过1000时会报错

一个牛人想出的解决办法

实现一个 tail_call_optimized 装饰器

#!/usr/bin/env python2.4
# This program shows off a python decorator(
# which implements tail call optimization. It
# does this by throwing an exception if it is
# it's own grandparent, and catching such
# exceptions to recall the stack.import sysclass TailRecurseException:def __init__(self, args, kwargs):self.args = argsself.kwargs = kwargsdef tail_call_optimized(g):"""This function decorates a function with tail calloptimization. It does this by throwing an exceptionif it is it's own grandparent, and catching suchexceptions to fake the tail call optimization.This function fails if the decoratedfunction recurses in a non-tail context."""def func(*args, **kwargs):f = sys._getframe()# 为什么是grandparent, 函数默认的第一层递归是父调用,# 对于尾递归, 不希望产生新的函数调用(即:祖父调用),# 所以这里抛出异常, 拿到参数, 退出被修饰函数的递归调用栈!(后面有动图分析)if f.f_back and f.f_back.f_back \and f.f_back.f_back.f_code == f.f_code:# 抛出异常raise TailRecurseException(args, kwargs)else:while 1:try:return g(*args, **kwargs)except TailRecurseException, e:# 捕获异常, 拿到参数, 退出被修饰函数的递归调用栈args = e.argskwargs = e.kwargsfunc.__doc__ = g.__doc__return func@tail_call_optimized
def factorial(n, acc=1):"calculate a factorial"if n == 0:return accreturn factorial(n-1, n*acc)print factorial(10000)

为了更清晰的展示开启尾递归优化前、后调用栈的变化和tail_call_optimized装饰器抛异常退出递归调用栈的作用, 我这里利用 pudb调试工具 做了动图 <br/>

开启尾递归优化前的调用栈

开启尾递归优化后(tail_call_optimized装饰器)的调用栈

通过pudb右边栏的stack, 可以很清晰的看到调用栈的变化.

因为尾递归没有调用栈的嵌套, 所以Python也不会报 RuntimeError: maximum recursion depth exceeded 错误了!

这里解释一下 sys._getframe() 函数:

sys._getframe([depth]):
Return a frame object from the call stack.
If optional integer depth is given, return the frame object that many calls below the top of the stack.
If that is deeper than the call stack, ValueEfror is raised. The default for depth is zero,
returning the frame at the top of the call stack.即返回depth深度调用的栈帧对象.import sysdef get_cur_info():print sys._getframe().f_code.co_filename  # 当前文件名print sys._getframe().f_code.co_name  # 当前函数名print sys._getframe().f_lineno # 当前行号print sys._getframe().f_back # 调用者的帧

补充

二分法查找大家应该听说过;就是一种快速查找的方法,时间复杂度低,逻辑简单易懂,总的来说就是不断的找出中间值,用中间值对比你需要找的实际值;若中间值大,则继续找左边;若中间值小,则继续找右边;可以看出二分法就是不断重复此上过程,所以就可以通过递归方式来实现二分法查找了!

#The binary search functiondef  Binary_Search(data_source,find_n):#判断列表长度是否大于1,小于1就是一个值if len(data_source) >= 1: #获取列表中间索引;奇数长度列表长度除以2会得到小数,通过int将转换整型     mid = int(len(data_source)/2) #判断查找值是否超出最大值   if find_n > data_source[-1]:                                    print('{}查找值不存在!'.format(find_n))exit()#判断查找值是否超出最小值elif find_n < data_source[0]:                                   print('{}查找值不存在!'.format(find_n))exit()#判断列表中间值是否大于查找值if data_source[mid]  > find_n:                                  print('查找值在 {} 左边'.format(data_source[mid]))#调用自己,并将中间值左边所有元素做参数Binary_Search(data_source[:mid],find_n)  #判断列表中间值是否小于查找值                   elif data_source[mid] < find_n:                                 #print('查找值在 {} 右边'.format(data_source[mid]))     #调用自己,并将中间值右边所有元素做参数      Binary_Search(data_source[mid:],find_n)else:#找到查找值print('找到查找值',data_source[mid])                          else:#特殊情况,返回查找不到print('{}查找值不存在!'.format(find_n))                          Data = [22,12,41,99,101,323,1009,232,887,97]
#列表从小到大排序
Data.sort()
#查找323
Binary_Search(Data,323)   执行结果:
找到查找值 323

Python 递归函数相关推荐

  1. python concat函数 多张表_教你用python递归函数求n的阶乘,优缺点及递归次数设置方式

    本文内容介绍了python递归函数求n的阶乘,优缺点及递归次数设置方式,具有很好的参考价值,希望对大家有所帮助.一起跟随小编过来看看吧! 递归函数两大特点: 1.能够调用函数自身 2.至少有一个出口( ...

  2. python递归函数讲解_Python递归函数实例讲解

    Python递归函数实例 1.打开Python开发工具IDLE,新建'递归.py'文件,并写代码如下: def digui(n): if n == 0 : print ('') return prin ...

  3. python递归函数错误使用方式

    1.python递归函数入坑: ip_list = [] def chooseDebugLinux():import randominfo = {}dockerip = random.randint( ...

  4. python递归函数的思想_Python递归函数实例讲解

    Python递归函数实例 1.打开Python开发工具IDLE,新建'递归.py'文件,并写代码如下: def digui(n): if n == 0 : print ('') return prin ...

  5. Python 递归函数 - Python零基础入门教程

    目录 一.Python 递归函数案例 1.Python 递归函数案例一 2.Python 递归函数案例二 3.Python 递归函数案例三 二.Python 递归函数总结 三.猜你喜欢 零基础 Pyt ...

  6. python派落塔问题_浅析python递归函数和河内塔问题

    关于递归函数: 函数内部调用自身的函数. 以n阶乘为例: f(n) = n ! = 1 x 2 x 3 x 4 x...x(n-1)x(n) = n x (n-1) ! def factorial(n ...

  7. python递归函数介绍

    python递归函数介绍 1.代码 def fibonacci(n):if n == 0:return 0elif n == 1:return 1else:return fibonacci(n-1) ...

  8. python中递归函数写法_《Python入门08》你知道Python递归函数怎么写吗~~

    点击上方的"小小白AI",选择"关注" 重磅知识,第一时间送达 前言 直入主题~~~ 1.什么是递归函数 前面深入介绍了如何创建和调用函数.你知道,函数可调用其 ...

  9. [转载] 递归函数python基例_python递归函数详解 python 递归函数使用装饰器

    参考链接: Python递归 移动宽带检查显示 CRM,radius状态不一致是怎么回事啊?您可以把电信宽带猫等设备重启试一下,如果仍然不行,可以拨打10000号请工作人员帮您查看.处理. 电信宽带诊 ...

  10. python递归函数例子_Python递归函数经典案例-汉诺塔问题

    汉诺塔 汉诺塔问题是递归算法学习的一个经典案例,首先来看下汉诺塔问题的相关描述: 汉诺塔问题起源于一个古老的印度传说,大梵天创世时制作了三根金刚石石柱,在第一根柱子上从上往下从小到大摞着64片金盘,婆 ...

最新文章

  1. php instr函数,oracle的instr函数用法
  2. 美国再出半导体新法案!1800亿谋求芯片制造振兴
  3. $state 新标签页
  4. 设置VS2010中使用IntelliTrace来进行调试
  5. 用mysql + node搭建一个简易工作列表网站
  6. SpringBoot:Could not autowire there is more than one bean of xx type
  7. 完成计算机组装工艺卡组装准备,计算机组装与维护(刘猛)教程方案.doc
  8. PPT Cookbook by Eric
  9. 为什么我们拒绝使用 Docker
  10. one hot 编码的实现
  11. 普林斯顿微积分读本:第 3 章 极限导论
  12. 【C#】万年历源码、农历、节日、节气齐全
  13. 《如何学商学》及听课感想
  14. Kindle3之中文乱码问题
  15. 免费电子书 工具 开发工具包 各类资源 下载 .
  16. 算法 - PNPoly解决点到多边形距离的问题
  17. 算法题练习系列之(一):守形数
  18. WARNING: You will need re2c 0.13.4 or later if you want to regenerate PHP parsers
  19. 三翼鸟,从做家务开始打破家居生活的“标准范式”
  20. 雪晴网上海沙龙+在线直播:如何成为数据科学家?

热门文章

  1. 2022保安员(初级)考试试题模拟考试平台操作
  2. 【shopee代运营分享】店铺有流量却没转化 ?东南亚新锐卖家教你破局
  3. raft协议(Replicated And Fault Tolerant)初探
  4. 硬核“毕业证”:5 位本科生带自研处理器芯片毕业,包云岗解读“一生一芯”计划...
  5. Proxmox ve 开启嵌套虚拟化
  6. 用opencv-python和matplotlib实现视频的颜色直方图的动态显示
  7. 分享个最近在做的自动化测试框架
  8. golang 本地缓存
  9. UE4如何提高在VR模式下的清晰度
  10. Android 11适配指南之系统相机拍照、打开相册,安卓app开发教程