浅谈递归的优化(JavaScript演示)

186 阅读3分钟

一、什么是递归?

递归就是在函数体内,自己调用自己。

二、递归的实例——实现斐波那契数列:

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)

再将32作为参数分别代入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

(下图为测试结果)

image.png

但是由于,在如同上述的递归过程中,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.分析:

这个方法没有再在函数体内再调用函数本身,所以,没有使用递归。

函数体内,最先定义了三个变量last1last2temp,其中last1last2都被赋值1。

下面定义了一个for循环,其中初始条件定义为:let i = 3,循环继续条件为:i <= n(n 为传参——斐波那契数列的项数),每次循环结束时i++

for循环内先将last1last2的和赋值给temp,再将last2的值赋值给last1,再将temp赋值给last2,实际上是将“原先”last1last2的和赋值给了“现在”的last2

i 实际上是一个计数器,不参与和其他值的运算,初始值设为3,是因为要避免斐波那契数列的前两项(均为1)参与到 for 循环中;

下面假定我们设定的实参,n=4:

itemp
32
43
last1last2
12
23

最终返回i === 4时的last2,其值为:3;可见与我们使用递归的结果一致。

(下图为测试结果)

image.png

这种不使用递归的方式,避免了递归导致的重复运算,从而提高了实现斐波那契数列的效率。

除了上述方式之外,还可以在不使用递归的前提下,通过定义一个初始数组方式,实现斐波那契数列的实例:

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] //返回数组
}

本文内容参考自饥人谷前端课程