递归的时间与空间复杂度

一、 递归的时间复杂度

递归算法的时间复杂度 = 递归次数 × \times × 每次递归的时间复杂度

递归次数:可以通过画递归树,数递归树的节点数,得到递归次数。

二、递归的空间复杂度

递归算法的空间复杂度 = 递归深度 × \times × 每次入栈的空间复杂度

三、例子

3.1 斐波那契数列求和

视频教程推荐:【程序日常】采用栈的方式还原递归整个执行流程_哔哩哔哩

以计算斐波那契数列为例,代码如下:

int fib(int i) { // 计算斐波那契数列的第 i 项
	if (i <= 0) return 0;
	if (i == 1) return 1;
	return fib(i - 1) + fib(i - 2);
}

假设计算的是 fib(4),计算过程过程如下:

  1. fib(4) 入栈,执行到第四行 return fib(3) + fib(2),按照从左往右计算,先算 fib(3)
  2. fib(3) 入栈。执行到第四行 return fib(2) + fib(1),按照从左往右计算,先算 fib(2)
  3. fib(2) 入栈。执行到第四行 return fib(1) + fib(0),按照从左往右计算,先算 fib(1)
  4. fib(1) 入栈。执行到第三行 if (i == 1) return 1,返回 1;
  5. fib(1) 出栈。返回的 1 交给 fib(2)fib(2) 继续执行第四行 return 1 + fib(0)
  6. fib(0) 入栈。执行到第二行 if (i <= 0) return 0 ,返回 0;
  7. fib(0) 出栈。返回的 0 交给 fib(2)fib(2) 继续执行第四行 return 1 + 0,返回 1;
  8. fib(2) 出栈。返回的 1 交给 fib(3)fib(3) 继续执行第四行 return 1 + fib(1)
  9. fib(1) 入栈。执行到第三行 if (i == 1) return 1,返回 1;
  10. fib(1) 出栈。返回的 1 交给 fib(3)fib(3) 继续执行第四行 return 1 + 1,返回 2;
  11. fib(3) 出栈。返回的 2 交给 fib(4)fib(4) 继续执行第四行 return 2 + fib(2);
  12. fib(2) 入栈。执行到第四行 return fib(1) + fib(0),按照从左往右计算,先算 fib(1)
  13. fib(1) 入栈。执行到第三行 if (i == 1) return 1,返回 1;
  14. fib(1) 出栈。返回的 1 交给 fib(2)fib(2) 继续执行第四行 return 1 + fib(0)
  15. fib(0) 入栈。执行到第二行 if (i <= 0) return 0 ,返回 0;
  16. fib(0) 出栈。返回的 0 交给 fib(2)fib(2) 继续执行第四行 return 1 + 0,返回 1;
  17. fib(2) 出栈。返回的 1 交给 fib(4)fib(4) 继续执行第四行 return 2 + 1,返回 3,程序结束。

3.2 递归次数和递归深度

从 3.1 的例子可以看出,递归次数影响时间复杂度,递归深度影响空间复杂度。时间复杂度比较好理解。此处不解释。

请添加图片描述

图 1

空间复杂度涉及到递归栈,结合图1,我们可以分析出以下三点:

  • 整个计算过程是线性进行的:比如 fib(3) + fib(2),这时并不会同时把 fib(3)fib(2)入栈,而是按顺序从左往右,把 fib(3) 入栈,执行 fib(3)

  • 递归深度影响空间复杂度:最深的一次递归所占用的空间(这里的空间包含之前递归所入栈的空间),就是整个计算过程中所能占用的最大空间了。整个过程是线性的,其中不断有出栈和入栈,占用的空间始终不会超过,最深的那次递归时,所用的空间。

  • 这个例子里的空间复杂度,就是 fib(4)入栈 + fib(3)入栈 + fib(2)入栈 + fib(1)入栈 所占用的空间(或者 fib(4)入栈 + fib(3)入栈 + fib(2)入栈 + fib(0)入栈 所占用的空间,在本例中他们是一样的)。

3.3 关于入栈

递归调用函数时,通常会将一些数据结构和变量入栈,以便在递归调用完成后能够恢复之前的状态并继续执行。具体入栈的内容包括:

  1. 函数的返回地址:在进入递归函数时,当前函数的返回地址会被压入栈中,以便在递归调用完成后能够返回到之前的函数继续执行。
  2. 函数的参数:递归调用时,需要将新的参数值传递给递归函数。这些参数值也会被入栈保存。
  3. 局部变量:递归调用会创建新的函数栈帧,每个栈帧都有自己的局部变量。这些局部变量也会被入栈保存。
  4. 临时变量:递归调用中使用的一些临时变量也会被入栈保存,以便在递归调用完成后能够恢复之前的值。

(需要注意的是,不同的编程语言和实现方式可能会有所不同,入栈的内容也可能会因具体情况而异。)

猜你喜欢

转载自blog.csdn.net/weixin_44286126/article/details/129922303