斐波那契数列-递归-尾递归

7,744 阅读3分钟

斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列以递归的方法定义:F(0)=0,F(1)=1, F(n)=F(n-1)+F(n-2)(n>=2,n∈N*)在现代物理、准晶体结构、化学等领域,斐波纳契数列都有直接的应用,为此,美国数学会从1963年起出版了以《斐波纳契数列季刊》为名的一份数学杂志,用于专门刊载这方面的研究成果。

完成 fibonacci 函数,接受 n 作为参数,可以获取数列中第 n 个数,例如:

fibonacci(1) // => 1
fibonacci(2) // => 1
fibonacci(3) // => 2

简单递归实现

function fibonacci (n) {
  if (n==1 || n==2) {
    return 1
  }
  return fibonacci(n-1)+fibonacci(n-2)
}
console.log(fibonacci(10))

递归函数就是会直接或间接的调用自身的一种函数。递归是一种强大的编程技术,它把一个问题分解为一组相似的子问题,每一个都用一个寻常解去解决。一般来说,一个递归函数调用自身去解决它的子问题。

递归的缺点:

  递归解题相对常用的算法如普通循环等,运行效率较低。因此,应该尽量避免使用递归,除非没有更好的算法或者某种特定情况,递归更为适合的时候。
在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储,因此递归次数过多容易造成栈溢出。 以上函数使用递归的方式进行斐波那契数列求和,但效率十分低,很多值会重复求值。所以使用 memoization方案进行优化。

memoization是一种将函数执行结果用变量缓存起来的方法。

当函数进行计算之前,先看缓存对象中是否计算结果,如果有,就直接从缓存对象中获取结果;如果没有,就进行计算,并将结果保存到缓存对象中。

const fibonacci = ((memo = [0, 1]) => {
  const fib = (n) => {
    let result = memo[n]
    if (typeof result !== "number") {
      result = fib(n - 1) + fib(n - 2)
      memo[n] = result
    }
    return result
  }
  return fib
})()

尾递归方案

一些语言提供了尾递归优化。如果一个函数返回自身递归调用的结果,那么调用过程会被替换为一个循环,它可以显著提高速度。 尾递归是一种在函数的最后执行递归调用语句的特殊形式的递归。

'use strict'
function Fib(n, n1, n2) {
    if(n == 0) {
        return n1
    }
    return Fib(n - 1, n2, n1 + n2)
}
// ES6的尾调用优化只在严格模式下开启,正常模式是无效的。

它的执行步骤如下,每次的n1就是要求当前的返回值,当执行到n减到0的时候,此时的n1就是我们要求的第n个数:

初始值 n1 = 0, n2 = 1

尾递归就是从最后开始计算, 每递归一次就算出相应的结果, 也就是说, 函数调用出现在调用者函数的尾部, 因为是尾部, 所以根本没有必要去保存任何局部变量。 直接让被调用的函数返回时越过调用者,返回到调用者的调用者去。

精髓:尾递归就是把当前的运算结果(或路径)放在参数里传给下层函数,深层函数所面对的不是越来越简单的问题,而是越来越复杂的问题,因为参数里带有前面若干步的运算路径。

尾递归是极其重要的,不用尾递归,函数的堆栈耗用难以估量,需要保存很多中间函数的堆栈。

汉诺塔baike.baidu.com/item/汉诺塔/34…

数据结构与算法caibaojian.com/learn-javas…