C++ 蹦床2025年3月21日 | 阅读 9 分钟 在 C++ 中,trampolining(蹦床技术) 是一种主要用于增强递归函数调用的技术。递归函数是避免问题复杂性并将其转化为多个简单问题的重要工具。然而,过度使用深度递归也有其缺点,例如可能导致调用栈溢出。这时,蹦床技术就派上用场了。 蹦床技术提供了一种将递归转换为迭代的方法,这样就不会有值被推入堆栈,从而避免了堆栈溢出,同时保持了递归逻辑的简单性。蹦床技术的核心思想不是直接递归,而是返回另一个 函数 来表示计算的下一步,可以是函数对象(或等效于函数对象的表示,如 lambda 或函数 指针)。然后,这些 对象 以这种方式进行迭代,由主循环来执行,从而有效地模仿了递归。 当与尾递归调用结合使用时,它通常很有用,因为递归调用是函数中执行的最后一个操作。但并不能保证特定的编译器会使用尾调用优化(TCO)来优化尾递归。蹦床技术通过控制函数调用在堆(heap)而不是在栈(stack)上进行,提供了一种手动实现相同效果的方法。 方法 1:基本函数指针/Lambda 蹦床如前所述,基本函数指针/Lambda 蹦床是实现 C++ 蹦床技术最简单的方法之一。这种递归形式涉及计算一个过程,该过程直接返回一个函数对象(如 lambda 或函数指针),该对象引用递归函数的下一阶段,而不是进行实际的递归。之后,蹦床机制会一遍又一遍地调用返回的函数对象,直到获得结果,从而消除了深度递归和堆栈溢出的风险。 程序让我们通过一个例子来说明 C++ 中的蹦床技术。 输出 Factorial of 5: Computing factorial_step(5, 1) Step 1: Invoking the next recursive step Computing factorial_step(4, 5) Step 2: Invoking the next recursive step Computing factorial_step(3, 20) Step 3: Invoking the next recursive step Computing factorial_step(2, 60) Step 4: Invoking the next recursive step Computing factorial_step(1, 120) Step 5: Invoking the next recursive step Computing factorial_step(0, 120) Base case reached with result = 120 Final result after 5 steps: 120 Factorial of 5 is 120 Factorial of 7: Computing factorial_step(7, 1) Step 1: Invoking the next recursive step Computing factorial_step(6, 7) Step 2: Invoking the next recursive step Computing factorial_step(5, 42) Step 3: Invoking the next recursive step Computing factorial_step(4, 210) Step 4: Invoking the next recursive step Computing factorial_step(3, 840) Step 5: Invoking the next recursive step Computing factorial_step(2, 2520) Step 6: Invoking the next recursive step Computing factorial_step(1, 5040) Step 7: Invoking the next recursive step Computing factorial_step(0, 5040) Base case reached with result = 5040 Final result after 7 steps: 5040 Factorial of 7 is 5040 Factorial of 0: Computing factorial_step(0, 1) Base case reached with result = 1 Final result after 0 steps: 1 Factorial of 0 is 1 Factorial of -3 (error case): ERROR! Error: Factorial is not defined for negative numbers. 说明提供的代码演示了使用 std::function 和 lambda 等 C++ 特性 的基于蹦床的递归函数。在此示例中,我们将使用三种概念:variant、std::function 和 lambda 函数来迭代计算数字的阶乘。这种方法通过将递归过程分解为离散的步骤来防止过度递归,并且这些步骤是在循环中计算的,而不是以可能导致调用栈反弹的方式进行递归调用。 这种方法可以防止堆栈溢出问题,同时简化了大多数语言内置的递归逻辑的使用。它还包括输入验证,并详细描述了分步计算过程,以提高计算的可靠性和可解释性。 step 结构封装了一个函数(作为 lambda),该函数计算如何进入下一个递归步骤。一个局部 head 包含两个结构,允许函数在递归步骤和最终 yield 之间切换。
如果找到一个 Step,它会调用存储在 Step 中的 lambda,以获取下一个 Step 或结果。这意味着递归采用迭代循环而不是调用栈利用来进行递归。它会持续重复,直到调用 Done 值;因此,最后一个值将作为结果返回。
复杂度分析时间复杂度 给定代码的时间复杂度是根据算法计算数字 n 的阶乘所使用的递归调用次数来分析的。 阶乘计算 如“factorial_step”中所述,一种解决递归方法是每一步都减小 n,直到最终达到 0。 关于此代码的一个重要说明是,对于任何给定的数字 n,将要进行的递归步骤数等于 n。之后,我们可以将构造的时间复杂度定义如下: 在步骤 i 的构造中有两个操作,还有一个乘法运算 (n*acc),它需要恒定的时间,即 O(1)。 因此,递归步骤的数量,也就是乘法的数量是 n。 蹦床函数 蹦床函数 通过调用存储在 Step 结构中的 lambda 函数来遍历所有 n 个递归步骤。 由于在最坏的情况下需要处理 n 个递归步骤,因此也需要 O(n) 的时间,因为它涉及调用 lambda 和更新状态。 因此,我们得出结论,使用这种蹦床方法获取阶乘的时间复杂度为 O(n),因为蹦床迭代 n 次,并且每次迭代都需要恒定的时间。 空间复杂度 时间复杂度以计算步骤来描述,而空间复杂度是存储计算中使用的变量和数据结构所需的空间量。 递归调用处理 通常,递归函数预计会利用调用栈上的 O(n) 空间,因为每次递归调用都会在栈上创建一个新的帧。 事实上,我们在之前的蹦床操作中遇到的递归通过使用循环被转化为迭代形式。空间复杂度为 O(1),因为蹦床函数不是递归的,而是执行循环来完成执行。 std::variant 和 Lambda 的内存 为了表示每个递归步骤,定义了一个包含 lambda 函数的 Step 对象。lambda 是按值捕获的,因此对于每个步骤,都会创建一个新的 lambda,它捕获 n 和 acc 的当前值。由于有 n 个步骤,并且在每个步骤都形成一个 lambda 函数,因此存储所有 lambda 的空间复杂度可能为 O(n)。之后,每个 lambda 存储当前步骤的信息,即 n 和 acc 的整数值。因此,每个 lambda 不需要显着的空间。 Variant 存储 在这里,std::variant 在任何给定时间都会保存 Done 或 Step 对象,但不是同时。此内存使用量是恒定的,表示为 O(1),因为 variant 只需要一个位置来存储这两个结构中的一个。 因此,代码的空间复杂度变为 O(n),因为代码需要存储 n 个 lambda,每个 lambda 都包含关于递归步骤的信息。但是,由于这些 lambda 是在 堆 上创建的,而不是在调用栈上创建的,因此代码不会出现堆栈溢出。 |
C++ 数据类型定义 C++ 中的数据类型对变量可以存储的不同信息种类进行分类。不同的数据类型具有不同的属性,例如它们可以保存的值范围以及它们可以执行的操作。整数 (int)、字符 (char)、浮点数......
阅读9分钟
C++ 程序使用用户提供的包含两个浮点值(表示变量 X 和 Y)的 vector 作为输入来计算皮尔逊相关系数。皮尔逊相关系数用于测量两个变量之间的线性关系。它通常取值介于 -1 之间……
5 分钟阅读
在竞争性编程、软件开发和系统编程的世界中,有效地管理独特的元素集合是一个常见的需求。C++ 标准模板库 (STL) 中的 set 容器完美地满足了这一需求。作为 STL 的基础数据结构之一,...
阅读 17 分钟
本文将详细阐述 C++ 中模板特化和模板重载之间的区别。模板特化提供了处理模板中编码的特定类型或类型组的方法。它允许覆盖模板机制提供的默认功能,用于一个或...
阅读 6 分钟
在计算机科学和算法问题解决中寻找各种问题的有效解决方案,经常会将我们引向一些核心组合逻辑的迷人谜题。其中一个问题是找出二值矩阵中最大加号 ('+') 的大小……
5 分钟阅读
在本文中,我们将讨论 C++ 中 rewinddir() 函数的语法、一些信息和示例。什么是 rewinddir() 函数?rewindir() 函数用于将目录流的位置恢复到目录的开头,dirp 必须调用 rewinddir() 函数。与 opendir() 函数类似,rewindir()...
阅读 3 分钟
引言:C++ 中的 monad(源自 Haskell 等函数式编程语言)表示一种设计模式,它允许在管理值、上下文或副作用的同时,以受控的方式链接操作。在 C++ 中,monad 不是原生内置的,但可以通过...
7 分钟阅读
在本文中,我们将讨论 C++ 中模板和继承之间的区别。在讨论它们的区别之前,我们必须了解模板和继承及其特性和局限性。什么是模板?模板是函数或类的蓝图或结构。库...
阅读 6 分钟
在本文中,我们将讨论如何在 C++ 中查找哈希冲突的索引,并提供几个示例。问题陈述:假设我们有一个数字 a 和一个包含 n 个元素的数组 P。有一个带有 'a' 个桶的哈希表...
5 分钟阅读
在本文中,我们将详细介绍在 C++ 中查找第 n 个埃尔米特数的程序。什么是埃尔米特数?埃尔米特数 Hn 是具有结果和的数类。埃尔米特数可以从下面的给定递归方程完全看出。它们...
阅读 4 分钟
我们请求您订阅我们的新闻通讯以获取最新更新。
我们提供所有技术(如 Java 教程、Android、Java 框架)的教程和面试问题
G-13, 2nd Floor, Sec-3, Noida, UP, 201301, India