算法与数据结构——了解复杂度(迭代与递归)

news/2024/9/18 3:50:47/文章来源:https://www.cnblogs.com/1873cy/p/18366749

复杂度分析

算法效率评估

在算法设计中,我们追求以下两个层面的目标。

  • 找到问题解法:算法需要再规定的输入范围内可靠地求得问题的正确解
  • 寻求最优解法:同一个问题可能存在多种解法,我们希望找到尽可能高效的算法。

也就是说,在能够解决问题的前提下,算法效率已经成为衡量算法优劣的主要评价指标,它包括以下两个度。

  • 时间效率:算法运行速度的快慢。
  • 空间效率:算法占用内存空间的大小。

复杂度分析能够体现算法运行所需要的时间和空间资源与输入数据大小之间的关系。它描述了随着输入数据的增加,算法执行所需要的时间和空间的增长趋势。

迭代与递归

在算法中,重复执行某个任务是很常见的,它与复杂度分析息息相关。因此,在介绍时间复杂度和空间复杂度之前,先了解如何在程序中实现重复执行任务,即来那个黄总基本的程序控制结构:迭代、递归

迭代

迭代(iteration)是一种重复执行某任务的控制结构。在迭代中,程序会在满足一定的条件下重复执行某代码,知道这个条件不再满足。

1.for循环

for循环是最常见的迭代形式之一,适合在预先知道迭代次数时使用

以下函数基于for循环实现了求和1+2+3+...+n,求和结果使用res记录。

int forLoop(int n){int res = 0;// 循环求和 1,2,3,4...,nfor (int i = 1; i <= n; i++){res += i;}return res;
}

流程图:

此求和函数的操作与输入数据大小n成正比,或者说成“线性关系”。实际上,时间复杂度描述的就是这个“线性关系”

2.while循环

与for循环类似,while循环也是一种实现迭代的方法。在while循环中,每轮都会先检查条件,如果条件为真,则继续执行否则就结束循环。

/*while 循环*/
int whileLoop(int n){int res = 0;int i = 1;while (i <= n){res += i;i++;}return res;
}

while循环比for循环自由度更高。在while循环中,我们可以自由地设计条件变量的初始化和更新步骤。

3.嵌套循环

我们可以在一个循环结构内嵌套另一个循环结构,下面以for循环为例:

/* 双层 for 循环 */
string nestedForLoop(int n) {ostringstream res;// 循环 i = 1, 2, ..., n-1, nfor (int i = 1; i <= n; ++i) {// 循环 j = 1, 2, ..., n-1, nfor (int j = 1; j <= n; ++j) {res << "(" << i << ", " << j << "), ";}}return res.str();
}

嵌套循环流程框图:

 在这种情况下,函数的操作数量与n2成正比,或者说算法运行时间和输入数据大小n成“平方关系”。我们可以继续添加嵌套循环,每一次嵌套都是一次“升维”,将会使其时间复杂度提高至“立方关系”“四次关系”,以此类推。

递归

递归(recursion)是一种算法策略,通过函数调用自身来解决问题。它主要包含两个阶段。

  • 递:程序不断深入地调用自身,通常传入更小或更简化的参数,知道达到“终止条件”。
  • 归:触发“终止条件”后,程序从最深层的递归函数开始逐层返回,汇聚每一层的结果。

而从实现的角度看,递归代码主要包含三个要素。

  • 终止条件:用于决定什么时候由“递”转为“归”。
  • 递归调用:对应“递”,函数调用自身,通常输入更小后更简化的参数。
  • 返回结果:对应“归”,将当前递归层级的结果返回至上一层。

观察以下代码,我们只需要调用函数recur(n)就可以完成1+2+3+...+n的计算

/*递归调用*/
int recur(int n){if (n == 1){return 1;}return n + recur(n - 1);
}

递归过程:

 

虽然从计算角度看,迭代与递归可以得到相同的结果,但他们代表了两种完全不同的思考和解决问题的范式。

  • 迭代:“自下而上”地解决问题。从最基础的步骤开始,然后不断重复或累加这些步骤,直到任务完成。
  • 递归:“自上而下”地解决问题。将原问题分解为更小的子问题,这些子问题和原问题具有相同的形式。接下来将子问题继续分解为更小的子问题,直到基本情况时停止(基本情况的解是已知的) 

上述求和函数为例,设问题 𝑓(𝑛) = 1 + 2 + ⋯ + 𝑛 。

  • 迭代:在循环中模拟求和过程,从1遍历到n,每轮执行求和操作,即可求得 𝑓(𝑛) 。
  • 递归:将问题分解为子问题 𝑓(𝑛) = n + 𝑓(𝑛 - 1),不断(递归地)分解下去,直至基本情况 𝑓(1) = 1 时 终止。 

 

1.调用栈

递归函数每次调用自身时,系统都会为新开启的函数分配内存,以存储局部变量、调用地址和其他信息等。这将导致两方面的结果。

  • 函数的上下文数据都存储在称为“栈帧空间”的内存区域中,直至函数返回后才会被释放。因此递归通常比迭代更加耗费内存空间
  • 递归调用函数会产生额外的开销。因此递归通常比循环的时间效率更低

在上述递归过程中,在触发终止条件前,同时存在n个未返回的递归函数,递归深度为n。

2.尾递归

如果函数在返回前的最后一步才进行递归调用,则该函数可以被编译器或解释器优化,使其在空间效率上与迭代相当。这种情况被称为尾递归(tail recursion)。

  • 普通递归:当函数返回到上一层级的函数后,需要继续执行代码,因此系统需要保存上一层调用的上下文
  • 尾递归:递归调用时函数返回前的最后一个操作,这意味着函数返回到上一层级后,无需继续执行其他操作,因此系统无需保存上一层函数的上下文。

以计算1+2+...+n为例,我们将结果设为函数参数res,从而实现尾递归:

/*尾递归*/
int tailRecur(int n, int res){// 终止条件if (n == 0)return res;// 尾递归调用return tailRecur(n - 1, res + n);
}

普通递归:求和操作是在“归”的过程中执行的,每层返回后都要执行一次求和操作。

尾递归:求和操作是在“递”的过程中执行的,“归”的过程只需层层返回。

尾递归过程:

注意:许多编译器或解释器并不支持尾递归优化。例如Python、Java、C++等默认不支持尾递归优化。

3.递归树

当处理与“分治”相关的算法问题时,递归往往比迭代的思路更加直观、代码更加易读。以“斐波那契数列”为例:

给定一个斐波那契数列 0,1,1,2,3,5,8,13,...,求该数列的第n个数字。

设斐波那契数列的第n个数字为 𝑓(𝑛) ,易得两个结论。

  • 数列的前两个数字为 𝑓(1) = 0 和 𝑓(2) = 1。
  • 数列中的每个数字是前两个数字的和,即 𝑓(𝑛)  =  𝑓(𝑛 - 1) +  𝑓(𝑛 - 2)。    

按照地推关系进行递归调用,将前两个数字作为终止条件,便可写出递归代码。调用fib(n)即可得到斐波那契数列的第n个数字:

/*斐波那契数列的第n个数字  0,1,1,2,3,5,8......  */
int fib(int n){if (n == 2 || n == 1)return n - 1;return fib(n - 1) + fib(n - 2);
}

 观察以上代码,我们在函数内递归调用了两个函数,这意味着从一个调用产生了两个调用分支。如图所示,这样不断递归调用下去,最终将产生一棵层数为n的递归树(recursion tree)。

从本质上看,递归体现了“将问题分解为更小的子问题”的思维范式,这种分治策略至关重要。

  • 从算法角度看,搜索、排序、回溯、分治、动态规划等许多重要算法策略直接或间接地应用了这种思维方式。
  • 从数据结构看,递归天然适合处理链表、树和图的相关问题,因为它们非常适合用分治思想进行分析

 

两者对比

  迭代   递归
实现方式 循环结构 函数调用自身
时间效率 效率通常较高,无函数调用开销 每次函数调用都会产生开销
内存使用 通常使用固定大小的内存空间 累计函数调用可能使用大量的栈帧空间
适用问题 适用于简单循环任务,代码直观、可读性好 适用于子问题分解,如树、图、分治、回溯等,代码结构简洁、清晰。

以上述递归函数为例,求和操作在递归的“归”阶段进行,这意味着最初被调用的函数实际上是最后完成其求和操作的,这种工作机制与栈的“先入后出”原则异曲同工。

事实上,“调用栈”和“栈帧空间”这类递归术语已经暗示了递归与栈之间的密切关系。

  • 递:当函数被调用时,系统会在“调用栈”上为该函数分配新的栈帧,用于存储函数的局部变量、参数、返回地址等数据
  • 归:当函数完成执行并返回时,对应的栈帧会被从“调用栈”上移除,恢复之前函数的执行环境。

总之,选择迭代还是递归取决于特定问题的性质。在编程实践中,权衡两者的优劣并根据情景选择合适的方法至关重要。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/783843.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

算法与数据结构——了解复杂度

复杂度分析 算法效率评估 在算法设计中,我们追求以下两个层面的目标。找到问题解法:算法需要再规定的输入范围内可靠地求得问题的正确解 寻求最优解法:同一个问题可能存在多种解法,我们希望找到尽可能高效的算法。也就是说,在能够解决问题的前提下,算法效率已经成为衡量算…

算法与数据结构——复杂度分析

复杂度分析 算法效率评估 在算法设计中,我们追求以下两个层面的目标。找到问题解法:算法需要再规定的输入范围内可靠地求得问题的正确解 寻求最优解法:同一个问题可能存在多种解法,我们希望找到尽可能高效的算法。也就是说,在能够解决问题的前提下,算法效率已经成为衡量算…

AI大模型快速生成题库-助力业务人效提升10+倍

一 现状问题 1、培训考核涉及的文件数量较多 当前,京东航空公司维修部门面临着人员规模的快速增长和持续的培训需求。根据民航局的规定,维修培训必须确保所有维修人员都能够完成对飞机维修相关文件的学习,这包括维修方案、维修工程管理手册、工作程序手册等共计12本手册以及…

存储系列之 Linux ext2 概述

来自:https://www.cnblogs.com/orange-CC/p/12673052.html存储系列之 Linux ext2 概述引言:学习经典永不过时。我们之前介绍过存储介质主要是磁盘,先介绍过物理的,后又介绍了虚拟的。保存在磁盘上的信息一般采用文件(file)为单位,磁盘上的文件必须是持久的,同时文件是通…

存储系列之 从ext2到ext3、ext4 的变化与区别

来自:https://www.cnblogs.com/orange-CC/p/12673073.html存储系列之 从ext2到ext3、ext4 的变化与区别引言:ext3 和 ext4 对 ext2 进行了增强,但是其核心设计并没有发生变化。所以建议先查看上上篇的《存储系列之 Linux ext2 概述 》,有了ext2的基础,看这篇就是so easy了…

【YashanDB知识库】生成迁移报告失败,报错未知类型错误异常:

【标题】YMP迁移 【问题分类】迁移报告 【关键字】迁移报告、未知类型错误异常 【问题描述】下载迁移报告时报错“未知类型错误异常:”,一长串英文日志报错:【问题原因分析】java版本不对,ymp仅支持java 8和11版本,用户环境用的21版本。【解决/规避方法】将java版本更新为8…

Visual Studio 2013 自定义动态库dll文件lib存放路径

前言全局说明Visual Studio 2013 自定义lib存放路径一、说明 环境: Windows 7 旗舰版 Visual Studio 2013二、设置说明 在一个功能比较全的项目中,有可能会引入第三方库来完成某些功能, 为了让目录结构、文件,清晰,会将引入的dll文件,放置到一个独立目录里。 这样方便管理…

7.路由器配置及使用

9.1 路由器的结构内存只读内存随机存储器ARP:广播信息 非易失随机存储器闪存9.2 路由器的工作原理9.3 路由表 注意,分两种路由表 路由器的路由表(基于端口的)三层交换机的路由表(基于vlan的)9.4 路由器的工作模式9-5 路由器的基本配置及常见命令 路由的配置方式*重要9-6 …

dedecms 两个常见漏洞的复现

dedecms系统的部分复现,还有更多的漏洞没有去复现和发现。侵权声明 本文章中的所有内容(包括但不限于文字、图像和其他媒体)仅供教育和参考目的。如果在本文章中使用了任何受版权保护的材料,我们满怀敬意地承认该内容的版权归原作者所有。 如果您是版权持有人,并且认为您的…

过滤器与拦截器

过滤器 与拦截器 参考https://www.cnblogs.com/Black-Ice/p/16248535.html过滤器 Filter Filter 基本介绍 过滤器 Filter 是 Sun 公司在 Servlet 2.3 规范中添加的新功能,其作用是对客户端发送给 Servlet 的请求以及对 Servlet 返回给客户端的响应做一些定制化的处理,例如校验…

独立站是什么?独立站的优势是什么?为什么要做独立站?一键三问

独立站是指一个完全独立的网站,由公司自主搭建和运营,包括独立的服务器、网站程序和单独的域名等等,完全不依赖于任何第三方平台。它最初用于区分与亚马逊、eBay、速卖通等各种第三方电商平台的区别。市面上有三种建站系统:全自主开发、基于开源软件建设的独立站和基于SaaS…

AP9196 DC-DC 输入3-40V 6A升压恒流电源管理芯 太阳能路灯方案

产品说明 AP9196 是一系列外围电路简洁的宽调光比升压调光恒流驱动器,适用于3-40V输入电压范围的LED照明领域。 AP9196 采用我司专利算法,可以实现高精度的恒流效果,输出电流恒流精度≤3%,电压工作范围为5-40V,可以轻松满足锂电池及中低压的应用需求,输出耐压仅由MOS 耐…