这篇文章主要是我对递归的学习总结
用递归来解决的问题要满足三个条件:
- 一个问题的解可以分解为几个子问题的解
- 这个问题与分解之后的子问题,除了数据规模不同,求解思路完全一样
- 存在递归终止条件
编写递归函数的思路:分析问题与子问题的关系,求出递归公式,找到递归的终止条件。
递归的例子:斐波那契数列
斐波那契数列数列的形式为 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)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。