什么是递归深度

递归深度就是递归函数在内存中,同时存在的最大次数
例如下面这段求阶乘的代码:
Java:

int factorial(int n) {
if (n == 1) {
return 1;
}
return factorial(n - 1) * n;
}

Python:

def factorial(n):
if n == 1:
return 1
return factorial(n-1) * n

C++:

int factorial(int n) {
if (n == 1) {
return 1;
}
return factorial(n - 1) * n;
}

n=100时,递归深度就是100。一般来说,我们更关心递归深度的数量级,在该阶乘函数中递归深度是O(n)O(n)O(n),而在二分查找中,递归深度是O(log(n))O(log(n))O(log(n))。在后面的教程中,我们还会学到基于递归的快速排序、归并排序、以及平衡二叉树的遍历,这些的递归深度都是(O(log(n))(O(log(n))(O(log(n))。注意,此处说的是递归深度,而并非时间复杂度。

太深的递归会内存溢出

首先,函数本身也是在内存中占空间的,主要用于存储传递的参数,以及调用代码的返回地址。
函数的调用,会在内存的栈空间中开辟新空间,来存放子函数。递归函数更是会不断占用栈空间,例如该阶乘函数,展开到最后n=1时,内存中会存在factorial(100), factorial(99), factorial(98) ... factorial(1)这些函数,它们从栈底向栈顶方向不断扩展。
当递归过深时,栈空间会被耗尽,这时就无法开辟新的函数,会报出stack overflow这样的错误。
所以,在考虑空间复杂度时,递归函数的深度也是要考虑进去的

Follow up:
尾递归:若递归函数中,递归调用是整个函数体中最后的语句,且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。(上例factorial函数满足前者,但不满足后者,故不是尾递归函数)
尾递归函数的特点是:在递归展开后该函数不再做任何操作,这意味着该函数可以不等子函数执行完,自己直接销毁,这样就不再占用内存。一个递归深度O(n)O(n)O(n)的尾递归函数,可以做到只占用O(1)O(1)O(1)空间。这极大的优化了栈空间的利用。
但要注意,这种内存优化是由编译器决定是否要采取的,不过大多数现代的编译器会利用这种特点自动生成优化的代码。在实际工作当中,尽量写尾递归函数,是很好的习惯。
而在算法题当中,计算空间复杂度时,建议还是老老实实地算空间复杂度了,尾递归这种优化提一下也是可以,但别太在意。

尾递归 递归函数中,递归调用是整个函数体中最后的语句,且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归,空间复杂度是O(1)的更多相关文章

  1. VC/MFC中通过CWebPage类调用javascript函数(给js函数传参,并取得返回值)

    转自:http://www.cnblogs.com/javaexam2/archive/2012/07/14/2632959.html ①需要一个别人写好的类CWebPage,将其对于的两个文件Web ...

  2. VC与JavaScript交互(三) --- CWebPage类调用javascript函数(给js函数传参,并取得返回值)

    ①需要一个别人写好的类CWebPage,将其对于的两个文件WebPage.h和WebPage.cpp添加到工程中. ②添加WebBrowser控件,在视图/对话框类的头文件中#include &quo ...

  3. jQuery find() 搜索所有段落中的后代 C# find() 第一个匹配元素 Func 有返回值 Action是没有返回值 Predicate 只有一个参数且返回值为bool 表达式树Expression

    所有p后代span Id为 TotalProject 的 select 标签 的后代 option标签 为选中的 text using System; using System.Collections ...

  4. 调用个别f5 负载端口为80的vs时,返回值为空的问题

    现状: vs负载端口为80并添加XFF,pool包含2个member,member的monitor端口为80&9000. 故障现象: 应用同事描述说再完全复制了一个member并添加到pool ...

  5. EF5中 执行 sql语句使用Database.ExecuteSqlCommand 返回影响的行数 ; EF5执行sql查询语句 Database.SqlQuery 带返回值

    一: 执行sql语句,返回受影响的行数 在mysql里面,如果没有影响,那么返回行数为  -1 ,sqlserver 里面  还没有测试过 using (var ctx = new MyDbConte ...

  6. stl中的transform()注意其与for_each的不同点(有无返回值)

    #include<iostream> using namespace std; #include"vector" #include"algorithm&quo ...

  7. JS中函数的本质,定义、调用,以及函数的参数和返回值

    要用面向对象的方式去编程,而不要用面向过程的方式去编程 对象是各种类型的数据的集合,可以是数字.字符串.数组.函数.对象…… 对象中的内容以键值对方式进行存储 对象要赋值给一个变量 var cat={ ...

  8. .NET中如何在同步代码块中调用异步方法

    更新记录 本文迁移自Panda666原博客,原发布时间:2021年7月2日. 在同步代码块中调用异步方法,方法有很多. 一.对于有返回值的Task 在同步代码块中直接访问 Task 的 Result ...

  9. shell调用函数返回值深入分析

    编写shell脚本过程中,我们经常会自定义一些函数,并根据函数的返回值不同来执行相应的流程,那么我们如何来获取函数的返回值呢? 首先shell中调用函数有两种方式: 第一种:value=`functi ...

随机推荐

  1. Linux搭建Nexus3.X构建maven私服

    备注:linux版本: ubuntu 同时已经部署好JDK8环境 一.linux安装nexus 1.创建文件夹并进入该目录 cd /usr/local && mkdir nexus & ...

  2. 【神经网络与深度学习】生成式对抗网络GAN研究进展(五)——Deep Convolutional Generative Adversarial Nerworks,DCGAN

    [前言]      本文首先介绍生成式模型,然后着重梳理生成式模型(Generative Models)中生成对抗网络(Generative Adversarial Network)的研究与发展.作者 ...

  3. IO流——字节流

    文件输出流 FileOutputStream:文件输出流是用于将数据写入 File,每次运行,都会覆盖之前文件中的数据 FileOutputStream(File file):创建一个向指定 File ...

  4. etcd备份与恢复

    目录 备份 恢复 备份 通常而言,etcd都是集群部署,其实并不需要额外备份,但实在是架不住猪队友误操作. 写一个简单的etcd备份脚本如下: #!/bin/bash set -e exec > ...

  5. Faiss的学习和入门文章

    可以看这里的文章: https://www.leiphone.com/news/201703/84gDbSOgJcxiC3DW.html https://waltyou.github.io/Faiss ...

  6. QT攻略——我在QT中遇到的那些坑

    (1)QUdpSocket接收数据 进入槽后,要用这种方式读取,否则可能会导致不发readyRead()信号 .while(udpSocket->bytesAvailable()){ udpSo ...

  7. drf面试题及总结

    drf面试题及总结 1.什么是前后端分离 2.什么是restful规范 3.模拟浏览器进行发送请求的工具 4.查找模板的顺序 5.什么是drf组件 6.drf组件提供的功能 7.drf继承过哪些视图类 ...

  8. Linux crond任务调度(定时任务),Linux磁盘分区/挂载

    一.crond任务调度 1.基本语法 crontab [选项] -e : 编辑 crontab定时任务 -l : 查询crontab -r : 删除当前用户所有的crontab任务 例子: 每分钟执行 ...

  9. AnyProxy代理

    背景:当一个公司测试团队有多个人的时候,只需搭建一个AnyProxy服务,其它小伙伴浏览器上打开AnyProxy页面,手机上设置代理就能抓到http.https请求了.解决了部分人电脑不正经的小伙伴f ...

  10. C#DataTable使用方法详解

    在项目中常常常使用到DataTable,假设DataTable使用得当,不仅能使程序简洁有用,并且可以提高性能,达到事半功倍的效果,现对DataTable的使用技巧进行一下总结. 1.添加引用 1 2 ...