1

这篇文章主要是我对递归的学习总结
用递归来解决的问题要满足三个条件

  1. 一个问题的解可以分解为几个子问题的解
  2. 这个问题与分解之后的子问题,除了数据规模不同,求解思路完全一样
  3. 存在递归终止条件

编写递归函数的思路:分析问题与子问题的关系,求出递归公式,找到递归的终止条件。
递归的例子:斐波那契数列
斐波那契数列数列的形式为 0 1 1 2 3 5 8 ..., 每一项都是前两项之和, 求解f(n)的值时,它就等于f(n-1)+f(n-2)。f(n)可以分解成规模更小的子问题,而终止条件就是f(0)=0, f(1)=1。根据递推公式和终止条件可以写出求解函数:

// 程序一
function fn(n){
    if(n===0) return 0
    if(n===1) return 1
    return fn(n-1) + fn(n-2)
}

由于函数的调用会使用栈来保存临时变量,函数执行完才出栈,递归层次过深会导致堆栈溢出。

使用循环代替递归
上面递归的例子可以改写为

// 程序二
function fn(n){
    let pre1 = 0
    let pre2 = 1
    let result = 0
    for (let i = 2; i <= n; i++) {
        result = pre1 + pre2
        pre1 = pre2
        pre2 = result
    }
    return result
}

尾递归优化

function fn(n, res1, res2) {
    if (n === 2) return res2
    return fn(n - 1, res2, res1 + res2)
}
console.log(fn(6, 0, 1)) // 初始值传入0和1

此时的n只是用来计数,和循环的方式差不多,是从“从前往后”计算,原始的递归方式是“从后往前”计算。
减少重复计算
由于递归过程中存在函数值重复计算的问题,我们可以将计算过的值存起来,当调用函数的参数相同时,就不必重复计算了,将以上函数改写为:

// 程序三
let memory = {}
function fn(n){
    if(n===0) return 0
    if(n===1) return 1
    if(memory[n]) {
        return memory[n]
    }
    let value = fn(n-1) + fn(n-2)
    memory[n] = value
    return memory[n]
}

上面的程序将调用过的函数的n与fn(n)键值对存储在memory中,第二次调用时就可以直接取值。
我们还可以编写一个为函数调用赋予记忆功能的函数:

const memoize = (fn) => {
  let memory = {}
  return function(n){
    if(memory[n]) return memory[n]
    memory[n] = fn.apply(this, arguments)
    return memory[n]
  }
}

memoize会接受一个函数,并返回另一个功能一致但带有记忆功能的函数。将该函数用于刚刚的例子,完整的代码:

// 函数四
function memoize(fn){
    let memory = {}
    return function(n){
        if(memory[n]) return memory[n]
        memory[n] = fn.apply(this, arguments)
        return memory[n]
    }
}

function fn(n){
    console.log('2')
    if(n===0) return 0
    if(n===1) return 1
    return fnMemo(n-1) + fnMemo(n-2) // 注意!此处也要用fnMemo函数
}
const fnMemo = memoize(fn)

toln
49 声望1 粉丝

前端开发@深圳