一、什么是递归?
递归就是在函数体内,自己调用自己。
二、递归的实例——实现斐波那契数列:
1.先上代码:
function fib(n) {
if(n === 1 || n === 2) return 1;
return fib(n-1) + fib(n-2);
}
console.log(fib(x)); // 其中x可以是任何自定义的正实数
2.分析:
1.我们定义了一个函数fib(n),其中包含了一个一行的if语句,以及if之外的一个return语句,在函数外,我们直接使用console.log(fib(x)),将设定的实参 x 对应的函数返回值打印出来
2.if语句的条件是:若参数为1或2(严格地),执行块是:返回 1
3.if之外,返回值是一个递归形式的和,意思就是输入的实参的前一个整数 x-1 和再前面一个整数 x-2 再重新代入函数 fib(n),然后再相加,得到返回值。
比如,我们设传入的 x=4 ,那么,第一次传入函数的参数就是整数 4 ,不满足 if 语句的条件,于是直接执行 if 之外的 return 语句,得到:return fib(4-1) + fib(4-2);等价于:fib(4) === fib(3) + fib(2)
再将3和2作为参数分别代入fib(n),按照以上逻辑,将分别得到:
1.fib(3) ===> fib(3) === fib(3-1) + fib(3-2)等价于:fib(3) === fib(2) + fib(1)也就等价于:
fib(3) === 2
2.fib(2) ===> fib(2) === 1
(实际上上面1.的fib(3) === fib(2) + fib(1),也需要单独运行一遍fib(2))
综上,fib(4)最终的返回值为3
(下图为测试结果)
但是由于,在如同上述的递归过程中,fib(2)被运行了两次,如果我们所设的实参够大,可以想见,重复运算的次数肯定很多,这也就是递归的缺点。
递归虽然写起来简便,但是却造成了资源的浪费。
三、使用非递归的方法实现斐波那契数列:
1.简单的非递归实现
function fn(n) {
let last1 = 1, last2 = 1, temp;
for(let i = 3; i <= n; i++) {
temp = last1 + last2;
last1 = last2;
last2 = temp;
}
return last2;
}
console.log(fn(n));
2.分析:
这个方法没有再在函数体内再调用函数本身,所以,没有使用递归。
函数体内,最先定义了三个变量last1、last2、temp,其中last1和last2都被赋值1。
下面定义了一个for循环,其中初始条件定义为:let i = 3,循环继续条件为:i <= n(n 为传参——斐波那契数列的项数),每次循环结束时i++;
for循环内先将last1和last2的和赋值给temp,再将last2的值赋值给last1,再将temp赋值给last2,实际上是将“原先”last1和last2的和赋值给了“现在”的last2。
i 实际上是一个计数器,不参与和其他值的运算,初始值设为3,是因为要避免斐波那契数列的前两项(均为1)参与到 for 循环中;
下面假定我们设定的实参,n=4:
| i | temp |
|---|---|
| 3 | 2 |
| 4 | 3 |
| last1 | last2 |
|---|---|
| 1 | 2 |
| 2 | 3 |
最终返回i === 4时的last2,其值为:3;可见与我们使用递归的结果一致。
(下图为测试结果)
这种不使用递归的方式,避免了递归导致的重复运算,从而提高了实现斐波那契数列的效率。
除了上述方式之外,还可以在不使用递归的前提下,通过定义一个初始数组方式,实现斐波那契数列的实例:
function fib(n) {
let arr = [0, 1, 1] //这是初始数组,包含了0和斐波那契数列的前两项
for(let i=3; i<=n; i++) {
arr[i] = arr[i-1] + arr[i-2] //从第三项开始,第n项的值等于前两项的和,存入数组
}
return arr[n] //返回数组
}
本文内容参考自饥人谷前端课程