目录
复习中ing,递归我总是迷迷糊糊的,这里有点醍醐灌顶。迭代是自下而上,从最基础的步骤开始,然后不断重复或累加这些步骤,直到任务完成。递归是自上而下,找到终止条件,然后向上“归”。
之前也刷过类似题:力扣刷题篇之递归-CSDN博客
参考2.2 ? 迭代与递归 - Hello 算法 (hello-algo.com)
「递归 recursion」是一种算法策略,通过函数调用自身来解决问题。它主要包含两个阶段。
而从实现的角度看,递归代码主要包含三个要素。
观察以下代码,我们只需调用函数?recur(n)
?,就可以完成?1+2+?+n 的计算:
/* 递归 */
int recur(int n) {
// 终止条件
if (n == 1)
return 1;
// 递:递归调用
int res = recur(n - 1);
// 归:返回结果
return n + res;
}
?该函数的递归过程:
虽然从计算角度看,迭代与递归可以得到相同的结果,但它们代表了两种完全不同的思考和解决问题的范式。
以上述求和函数为例,设问题
递归函数每次调用自身时,系统都会为新开启的函数分配内存,以存储局部变量、调用地址和其他信息等。这将导致两方面的结果。
如图 2-4 所示,在触发终止条件前,同时存在n个未返回的递归函数,递归深度为 n。
在实际中,编程语言允许的递归深度通常是有限的,过深的递归可能导致栈溢出错误。
有趣的是,如果函数在返回前的最后一步才进行递归调用,则该函数可以被编译器或解释器优化,使其在空间效率上与迭代相当。这种情况被称为「尾递归 tail recursion」。
以计算?1+2+?+n?为例,我们可以将结果变量?res
?设为函数参数,从而实现尾递归:
/* 尾递归 */
int tailRecur(int n, int res) {
// 终止条件
if (n == 0)
return res;
// 尾递归调用
return tailRecur(n - 1, res + n);
}
尾递归的执行过程如图所示。对比普通递归和尾递归,两者的求和操作的执行点是不同的。
请注意,许多编译器或解释器并不支持尾递归优化。例如,Python 默认不支持尾递归优化,因此即使函数是尾递归形式,仍然可能会遇到栈溢出问题。
当处理与“分治”相关的算法问题时,递归往往比迭代的思路更加直观、代码更加易读。以“斐波那契数列”为例。
Question
给定一个斐波那契数列?0,1,1,2,3,5,8,13,…?,求该数列的第?�?个数字。
设斐波那契数列的第?n个数字为?f(n)?,易得两个结论。
按照递推关系进行递归调用,将前两个数字作为终止条件,便可写出递归代码。调用?fib(n)
?即可得到斐波那契数列的第?n?个数字:
/* 斐波那契数列:递归 */
int fib(int n) {
// 终止条件 f(1) = 0, f(2) = 1
if (n == 1 || n == 2)
return n - 1;
// 递归调用 f(n) = f(n-1) + f(n-2)
int res = fib(n - 1) + fib(n - 2);
// 返回结果 f(n)
return res;
}
观察以上代码,我们在函数内递归调用了两个函数,这意味着从一个调用产生了两个调用分支。如图所示,这样不断递归调用下去,最终将产生一棵层数为?n?的「递归树 recursion tree」。
从本质上看,递归体现了“将问题分解为更小子问题”的思维范式,这种分治策略至关重要。
总之,选择迭代还是递归取决于特定问题的性质。在编程实践中,权衡两者的优劣并根据情境选择合适的方法至关重要。?