可能对递归理解的还不够!还差得远!

应用开发2025-11-03 22:09:1739871

上周小结的对递时候就提到了,在「算法汇总」里算法性能分析这块还没补全,归理够还后序会补上,对递所以这就来了,归理够还要分析递归的对递性能咱就分析个清清楚楚!

之前在通过一道面试题目,讲一讲递归算法的归理够还时间复杂度!中详细讲解了递归算法的时间复杂度,但没有讲空间复杂度。对递

本篇讲通过求斐波那契数列和二分法再来深入分析一波递归算法的归理够还时间和空间复杂度,细心看完,对递会刷新对递归的归理够还认知!

递归求斐波那契数列的性能分析

先来看一下求斐波那契数的递归写法。

int fibonacci(int i) {        if(i <= 0) return 0;        if(i == 1) return 1;        return fibonacci(i-1) + fibonacci(i-2); } 

对于递归算法来说,对递代码一般都比较简短,归理够还从算法逻辑上看,对递所用的归理够还存储空间也非常少,但运行时需要内存可不见得会少。对递

时间复杂度分析

来看看这个求斐波那契的递归算法的时间复杂度是多少呢?

在讲解递归时间复杂度的时候,我们提到了递归算法的时间复杂度本质上是要看: 递归的次数 * 每次递归的时间复杂度。香港云服务器

可以看出上面的代码每次递归都是O(1)的操作。再来看递归了多少次,这里将i为5作为输入的递归过程 抽象成一颗递归树,如图:

从图中,可以看出f(5)是由f(4)和f(3)相加而来,那么f(4)是由f(3)和f(2)相加而来 以此类推。

在这颗二叉树中每一个节点都是一次递归,那么这棵树有多少个节点呢?

我们之前也有说到,一棵深度(按根节点深度为1)为k的二叉树最多可以有 2^k - 1 个节点。

所以该递归算法的时间复杂度为 O(2^n) ,这个复杂度是非常大的,随着n的增大,耗时是指数上升的。

来做一个实验,大家可以有一个直观的感受。

以下为C++代码,来测一下,让我们输入n的亿华云时候,这段递归求斐波那契代码的耗时。

#include <iostream> #include <chrono> #include <thread> using namespace std; using namespace chrono; int fibonacci(int i) {        if(i <= 0) return 0;        if(i == 1) return 1;        return fibonacci(i - 1) + fibonacci(i - 2); } void time_consumption() {     int n;     while (cin >> n) {         milliseconds start_time = duration_cast<milliseconds >(             system_clock::now().time_since_epoch()         );         fibonacci(n);         milliseconds end_time = duration_cast<milliseconds >(             system_clock::now().time_since_epoch()         );         cout << milliseconds(end_time).count() - milliseconds(start_time).count()             <<" ms"<< endl;     } } int main() {     time_consumption();     return 0; } 

根据以上代码,给出几组实验数据:

测试电脑以2015版MacPro为例,CPU配置:2.7 GHz Dual-Core Intel Core i5

测试数据如下:

n = 40,耗时:837 ms n = 50,耗时:110306 ms

可以看出,O(2^n)这种指数级别的复杂度是非常大的。

所以这种求斐波那契数的算法看似简洁,其实时间复杂度非常高,一般不推荐这样来实现斐波那契。

其实罪魁祸首就是这里的两次递归,导致了时间复杂度以指数上升。

return fibonacci(i-1) + fibonacci(i-2); 

可不可以优化一下这个递归算法呢。主要是减少递归的调用次数。

来看一下如下代码:

// 版本二 int fibonacci(int first, int second, int n) {     if (n <= 0) {         return 0;     }     if (n < 3) {         return 1;     }     else if (n == 3) {         return first + second;     }     else {         return fibonacci(second, first + second, n - 1);     } } 

这里相当于用first和second来记录当前相加的两个数值,此时就不用两次递归了。

因为每次递归的时候n减1,即只是递归了n次,所以时间复杂度是 O(n)。网站模板

同理递归的深度依然是n,每次递归所需的空间也是常数,所以空间复杂度依然是O(n)。

代码(版本二)的复杂度如下:

时间复杂度:O(n) 空间复杂度:O(n)

此时再来测一下耗时情况验证一下:

#include <iostream> #include <chrono> #include <thread> using namespace std; using namespace chrono; int fibonacci_3(int first, int second, int n) {     if (n <= 0) {         return 0;     }     if (n < 3) {         return 1;     }     else if (n == 3) {         return first + second;     }     else {         return fibonacci_3(second, first + second, n - 1);     } } void time_consumption() {     int n;     while (cin >> n) {         milliseconds start_time = duration_cast<milliseconds >(             system_clock::now().time_since_epoch()         );         fibonacci_3(0, 1, n);         milliseconds end_time = duration_cast<milliseconds >(             system_clock::now().time_since_epoch()         );         cout << milliseconds(end_time).count() - milliseconds(start_time).count()             <<" ms"<< endl;     } } int main() {     time_consumption();     return 0; } 

测试数据如下:

n = 40,耗时:0 ms n = 50,耗时:0 ms

大家此时应该可以看出差距了!!

空间复杂度分析

说完了这段递归代码的时间复杂度,再看看如何求其空间复杂度呢,这里给大家提供一个公式:递归算法的空间复杂度 = 每次递归的空间复杂度 * 递归深度

为什么要求递归的深度呢?

因为每次递归所需的空间都被压到调用栈里(这是内存管理里面的数据结构,和算法里的栈原理是一样的),一次递归结束,这个栈就是就是把本次递归的数据弹出去。所以这个栈最大的长度就是递归的深度。

此时可以分析这段递归的空间复杂度,从代码中可以看出每次递归所需要的空间大小都是一样的,所以每次递归中需要的空间是一个常量,并不会随着n的变化而变化,每次递归的空间复杂度就是O(1)。

在看递归的深度是多少呢?如图所示:

递归第n个斐波那契数的话,递归调用栈的深度就是n。

那么每次递归的空间复杂度是O(1), 调用栈深度为n,所以这段递归代码的空间复杂度就是O(n)。

int fibonacci(int i) {        if(i <= 0) return 0;        if(i == 1) return 1;        return fibonacci(i-1) + fibonacci(i-2); } 

最后对各种求斐波那契数列方法的性能做一下分析,如题:

可以看出,求斐波那契数的时候,使用递归算法并不一定是在性能上是最优的,但递归确实简化的代码层面的复杂度。

二分法(递归实现)的性能分析

带大家再分析一段二分查找的递归实现。

int binary_search( int arr[], int l, int r, int x) {     if (r >= l) {         int mid = l + (r - l) / 2;         if (arr[mid] == x)             return mid;         if (arr[mid] > x)             return binary_search(arr, l, mid - 1, x);         return binary_search(arr, mid + 1, r, x);     }     return -1; } 

都知道二分查找的时间复杂度是O(logn),那么递归二分查找的空间复杂度是多少呢?

我们依然看 每次递归的空间复杂度和递归的深度

首先我们先明确这里的空间复杂度里面的n是什么?二分查找的时候n就是指查找数组的长度,也就是代码中的arr数组。

每次递归的空间复杂度可以看出主要就是参数里传入的这个arr数组,即:O(n)。

再来看递归的深度,二分查找的递归深度是logn ,递归深度就是调用栈的长度,那么这段代码的空间复杂度为 n * logn = O(nlogn)。

有同学问了:为什么网上很多人说递归二分查找的空间复杂度是O(logn),而不是O(nlogn)呢?

其实还是因为这个数组,我们可以把这个数组放到外面而不是放在递归函数参数里里,代码如下:

int arr[] = {2, 3, 4, 5, 8, 10, 15, 17, 20}; int binary_search(int l, int r, int n) {     if (r >= l) {         int mid = l + (r - l) / 2;         if (arr[mid] == n)             return mid;         if (arr[mid] > n)             return binary_search(l, mid - 1, n);         return binary_search(mid + 1, r, n);     }     return -1; } 

看这份代码将arr数组定义为全局变量,而不放在递归循环里,那么每层递归的空间复杂度是O(1),递归深度为O(logn),整体空间复杂度为 1 * logn = O(logn)。

总结

本章我们详细分析了递归实现的求斐波那契和二分法的空间复杂度,同时也对时间复杂度做了分析。

特别是两种递归实现的求斐波那契数列,其时间复杂度截然不容,我们还做了实验,验证了时间复杂度为O(2^n)是非常耗时的。

通过本篇大家应该对递归算法的时间复杂度和空间复杂度有更加深刻的理解了。

本文地址:http://www.bzuk.cn/html/220b33299447.html
版权声明

本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。

热门文章

全站热门

石材电脑锯开料机使用教程(学习如何正确使用石材电脑锯开料机)

一.以文件名查找: 1. find 命令 由于find具有强大的功能,所以它的选项也很多,其中大部分选项都值得我们花时间来了解一下。即使系统中含有网络文件系统( NFS),find命令在该文件系统中同样有效,只你具有相应的权限。 3、find命令选项 -name 2. locate 命令 locate filename 发现包含字符串“filename”的文件名。这比find命令更容易。但是基于数据库(通常在夜间重建),所以你无法找到刚刚存到文件系统的文件。为了强制立即更新数据库,作为超级用户可以使用:updatedb& (中间没有空格) 3. which命令 which executeable_name 二.以文件内容查找 1. grep -n 字符串名字 /filepath/filename 查看文件内容的特殊方法 1. 假如你只想看文件的前5行,可以使用head命令,如: head -5 /etc/passwd 2. 假如你想查看文件的后10行,可以使用tail命令,如: tail -20 /etc/passwd tail -f /var/log/messages 参数-f使tail不停地去读最新的内容,这样有实时监视的效果 tail -f /var/log/messages 按Ctrl+C后,直接从脚本退出到提示符下了

图解演示环境版本:本机系统: WIN7虚拟机:VMware Workstation 8 (英文版)安装目标:Ubuntu Desktop 12.04 LTS  (请点击这里)先下载好iso镜像文件详细过程图解:0. 初始画面,点击“Create a New Virtual Machine”(左上Ubuntu为本人已有开发环境机,请忽略)1. 点击“Custom(自定义)”2. 无需选择,直接Next(上面是选Workstation版本的兼容性的,这里默认为当前版本8.0,之前版本的不同在于Limitations(局限),如内存更少,不支持HD Audio等)3. 选择“I will install the operating system later”这里无严格要求的同学,是可以选择第二项“Installer disc image file (ios)”的,之后会VMware会自动得知你的iso是Linux(Ubuntu),只要求你输入Full name,和用户名密码等简单的用户设定,但是这是一个Easy install,如VMware原文所说“When the New Virtual Wizard detects an operating system that supports Easy Install, the wizard prompts you for information about the guest operating system. After the virtual machine is created, the guest operating system installation is automated and VMware Tools is installed.” 我觉得是因为这个OS的自动安装,不完全,导致一些核心命令无法使用、无反应等一些问题。所以有更高要求的同学,不能选这项,需要完全、自定义的安装。4. 在Version下选择“Ubuntu”,注:64位Ubuntu需要选下面那个“Ubuntu 64-bit”5. 设置虚拟机名称(即每次启动VMware左上方显示的名字),之后选择你想的在WIN7里的安装路径(默认在C盘,很不方便)。6. Number of processors(处理器个数)选择为2我是i7处理器,配置较好无压力的,感觉双核比单核好一些(假如没用VMware不会这么设计,但是对于更多的,没必要),下面那个应该没必要选,有非常懂的同学,请留言赐教。7. 内存大小选择,使用自动推荐的1G内存(本机内存8G)。同学们在虚拟机里,应该不会跑什么惊天地泣鬼神的大程序,内存大不等于快,而是更多的数据放在内存里而非硬盘里,对于内存消耗大的程序、系统会变快。去年做本科毕设的时候,调整过虚拟机的内存从1G为2G,结果竟然变慢了,应该是外面WIN7被占用了的问题。8. Network Type网络类型选择,本次选择默认的“NAT”注:这里有一点本人经历的非常重要需要说明,使用“NAT”的话,需要外面的WIN7使用一根线连接上网,才能在Ubuntu里上网(如同Ubuntu是你的真正OS的感觉,不需要手工配置任何IP信息),不能默认使用无线连接。这点对有些笔记本同学可能会造成麻烦。当然不是说不能通过手动配置IP相关解决,但是为了避免每次都配置的麻烦,请直接使用“bridged”桥接手动配置。9. 默认即可,直接“Next”10. 默认即可,直接“Next”第三项为直接划分硬盘给该虚拟机使用,意思应为绕过WIN7的那个文件夹管理,直接给虚拟机只用一块硬盘空间,有高级需要的同学可以选择。所以,注:默认的那个可以轻松实现copy,move,当你想拷给另外一个人,或者换机器的时候。11. 磁盘选择,默认即可,直接“Next”12. 选择“Store virtual disk as a single file”上面那个方框,是说现在就立即分20G给这个虚拟机,假如不够,还是会一点一点随着你的使用增加(跟不选一样)。假如同时没有很多个虚拟机装在WIN7上,或者硬盘空间太大又不放东西,可选。13. 虚拟机文件的存放地址,选个D盘的位置就行了。14. 点击“Finish”,完成了虚拟机的配置工作这里点击“Customize Hardware”的话,有机会对前面不满意的虚拟机硬件设置(处理器个数,内存大小等)重新设置,所以前面不满意的同学,不用点cancel重来,实际上在以后的使用过程,也是可以随时改变虚拟机的配置的,这点不用担心。15. 完成后,可以看到左上角多出了“Ubuntu 12.04”,先别急着Power on,还没装ubuntu呢。。。点击“Edit virtual machine settings”16. 在弹出的settings里,点击“CD/DVD(IDE)”,然后在右侧点击“Use ISO image file”,再选择你开始下载好的Ubuntu 12.04的iso镜像文件的路径然后点“OK”。17. 启动虚拟机,即点击step 15里的“Power on this virtual machine”,之后Ubuntu 12.04开始了安装,先选择语言,然后点击“Install Ubuntu”18. 假如选择“Download updates while installing”为安装过程直接安装最近的更新,假如选择“Install this third-party software”为安装第三方软件19. 选择“Something else”,将要对虚拟机的20G硬盘做手动分区20. 点击“New Partation Table”(新建分区表)21. 在弹出的对话框里,选择“Contunie”22. 选中新出现的“free space”(空闲空间),点击“Add”23. 注意下图中的“Primary”,“Beginning”, “Ext4 ...”均为默认,不需要修改;数字为大小,以MB为单位(注:不用追求1024凑整,硬盘实际上是凑不整的。。。),这里选择10000=10G;最后的“Mount point(挂载点)”下拉列表中,选中“/”,完成该步,点“OK”注意:“/ ” 建议大小在5GB以上。(根据关于“Ubuntu手动分区”的多个相关文章一致得来)非常注意:本人上次弄了个6G,结果进去下libraries,一下就满了,那叫一个悲剧!所以,同学们千万别抱着“5G以上”来想,ubuntu应该自己就占了4、5G,不想悲剧的同学至少8G以上吧,20G确实不大,但是假如打算长期的同学,应该不会使用虚拟机了,20G跑程序,绰绰有余,等喜欢了熟悉了,再来个真的吧。24. 再次选中“free space”(同step 22图中),点击“Add”;注意下图中“Logical”,“Beginning”均为默认,大小选择1000(1G);在Use as的下拉列表中选择“swap area”,注:最后的下拉列表为灰色,意为swap area不用选择挂载点;完成该步,点“OK”注意:“swap area” 即交换分区,建议大小是物理内存的1~2倍。(根据关于“Ubuntu手动分区”的多个相关文章一致得来)不需要太大,1G足以。25. 再次选中“free space”(同step 22图中),点击“Add”;注意下图中“Logical”,“Beginning”, “Ext4 ...”均为默认;注:大小选择也为默认,即所有的剩余空间;最后的“Mount point”下拉列表中,选中“/home”;完成该步,点“OK”注意:“/home” 存放普通用户的数据,是普通用户的宿主目录,建议大小为剩下的空间。(根据关于“Ubuntu手动分区”的多个相关文章一致得来)注:三个分区的顺序不要变,因为/home在最后便于默认选择“剩余的空间”,避免手工分配。26. 至此,所有分区工作已经完成,如下图所示。注:假如不满意可以点击“Revert(还原)”来重新分区,直到满意和准确无误为止。假如感到满意,点击“Install Now”注:上图为悲剧图,6G的/是不够的,这个图没有更新,仅供参考,不比看数字。27. 选择你所在的时区,自动调整时间,夏令时什么的手动调不方便,之后都点击“Continue”以继续28. 键盘选择US,一般国内买的电脑都是这样的,可根据情况自己选择29. Ubuntu的个人设置,根据自己需要填写用户名密码等30. 最后安装完成,点击“Restart Now”重启Ubuntu即可31. 停止在如下画面,按“回车”即可至此,全部安装过程完毕,我们可以进入到Ubuntu 12.04的桌面工作了。一定要注意:由于未使用自动安装,所以现在我们的虚拟机不含有VM Tools,导致无法全屏虚拟机等等问题,需要安装VM tools,详情请搜索即可。

解析电脑蓝屏重启(电脑蓝屏重启的关键问题及解决方法)

福克斯特电脑使用教程(轻松掌握福克斯特电脑操作技巧)

windows连接ubuntu

7zip 是一款开源的归档应用程序,开始是为 Windows 系统而开发的。它能对多种格式的档案文件进行打包或解包处理,除了支持其原生的 7z 格式的文档外,还支持包括 XZ、GZIP、TAR、ZIP 和 BZIP2 等这些格式。 通常,7zip 也用来解压 RAR、DEB、RPM 和 ISO 等格式的文件。除了简单的归档功能,7zip 还具有支持 AES-256 算法加密以及自解压和建立多卷存档功能。在支持 POSIX 标准的系统上(Linux、Unix、BSD),原生的 7zip 程序被移植过来并被命名为 p7zip(“POSIX 7zip” 的简称)。下面介绍如何在 Linux 中安装 7zip (或 p7zip)。在 Debian、Ubuntu 或 Linux Mint 系统中安装 7zip在基于的 Debian 的发布系统中存在有三种 7zip 的软件包。     p7zip: 包含 7zr(最小的 7zip 归档工具),仅仅只能处理原生的 7z 格式。 p7zip-full: 包含 7z ,支持 7z、LZMA2、XZ、ZIP、CAB、GZIP、BZIP2、ARJ、TAR、CPIO、RPM、ISO 和 DEB 格式。 p7zip-rar: 包含一个能解压 RAR 文件的插件。建议安装 p7zip-full 包(不是 p7zip),因为这是最完全的 7zip 程序包,它支持很多归档格式。此外,假如您想处理 RAR 文件话,还需要安装 p7zip-rar 包,做成一个独立的插件包的原因是因为 RAR 是一种专有格式。复制代码代码如下: $ sudo apt-get install p7zip-full p7zip-rar 在 Fedora 或 CentOS/RHEL 系统中安装 7zip基于红帽的发布系统上提供了两个 7zip 的软件包。     p7zip: 包含 7za 命令,支持 7z、ZIP、GZIP、CAB、ARJ、BZIP2、TAR、CPIO、RPM 和 DEB 格式。 p7zip-plugins: 包含 7z 命令,额外的插件,它扩展了 7za 命令(例如支持 ISO 格式的抽取)。在 CentOS/RHEL 系统中,在运行下面命令前您需要确保 EPEL 资源库 可用,但在 Fedora 系统中就不需要额外的资源库了。复制代码代码如下:$ sudo yum install p7zip p7zip-plugins 注意,跟基于 Debian 的发布系统不同的是,基于红帽的发布系统没有提供 RAR 插件,所以您不能使用 7z 命令来抽取解压 RAR 文件。使用 7z 创建或提取归档文件一旦安装好 7zip 软件后,就可以使用 7z 命令来打包解包各式各样的归档文件了。7z 命令会使用不同的插件来辅助处理对应格式的归档文件。使用 “a” 选项就可以创建一个归档文件,它可以创建 7z、XZ、GZIP、TAR、 ZIP 和 BZIP2 这几种格式的文件。假如指定的归档文件已经存在的话,它会把文件“附加”到存在的归档中,而不是覆盖原有归档文件。复制代码代码如下:$ 7z a 使用 “e” 选项可以抽取一个归档文件,抽取出的文件会放在当前目录。抽取支持的格式比创建时支持的格式要多的多,包括 7z、XZ、GZIP、TAR、ZIP、BZIP2、LZMA2、CAB、ARJ、CPIO、RPM、ISO 和 DEB 这些格式。复制代码代码如下:$ 7z e 解包的另外一种方式是使用 “x” 选项。和 “e” 选项不同的是,它使用的是全路径来抽取归档的内容。复制代码代码如下:$ 7z x 要查看归档的文件列表,使用 “l” 选项。复制代码代码如下:$ 7z l 要更新或删除归档文件,分别使用 “u” 和 “d” 选项。复制代码代码如下: $ 7z u $ 7z d 要测试归档的完整性,使用:复制代码代码如下:$ 7z t

解决电脑上网显示DNS错误的方法(快速修复网络连接问题,畅享无阻上网体验)

热门文章

友情链接

滇ICP备2023006006号-33