前言
本人是一个刚入行的菜鸡前端程序员,写这个文章的目的只是为了记录自己学习的笔记与成果,如有不足请大家多多指点。
在之前的文章中,我们学习了使用不同的可迭代数据结构。后面我们将要使用一种特殊的方法使操作树和图数据结构变得更简单,那就是递归。在学习树和图之前,我们需要先理解递归是如何工作的。
递归
递归是一种解决问题的方法,它从解决问题的各个小部分开始,直到解决最初的大问题。递归通常涉及函数调用自身。
递归函数是像下面这样能够最直接调用自身的方法或函数
function recursiveFunction(someParam) {
recursuveFunction(someParam)
}
能够像下面这样间接调用自身的函数,也是递归函数
function recursuveFunction1(someParam) {
recursuveFunction2(someParam)
}
function recursiveFunction2(someParam) {
recursuveFunction1(someParam)
}
假设现在必须要执行 recursuveFunction,就上述情况而言,它会一直执行下去。因此,每个递归函数都必须有基线条件,即一个不再递归调用的条件(停止点),以防无限递归。
function understandRecursion(doIunderstandRecursion) {
const recursionAnswer = confirm('Do you understand Recursion?')
if(recursionAnswer === true) { // 基线条件或停止点
return true
}
understandRecursion(recursionAnswer)
}
understandRecursion 函数会不断地调用自身,知道 recursionAnswer 为真(true)。 recursionAnswer 为真就是上述代码的基线条件。
下面我们就来看一些著名的递归算法。
计算一个数的阶乘
数 n 的阶乘,定义为 n!,表示从1到n的整数的乘积。
5的阶乘表示 5!,和 5 x 4 x 3 x 2 x 1 相等,结果是120。
迭代阶乘
如果尝试表示计算任意数 n 的阶乘步骤,可以将步骤定义如下:(n) * (n-1) * (n-2) * ... *1。
可以使用循环来写一个计算一个数阶乘的函数。
function factorialIterative(num) {
if(num < 0) return undefined
let total = 1
for (let n = num; n > 1; n--) {
total = total * n
}
return total
}
递归阶乘
我们可以试着用递归来重写 factorialIterative 函数,但是首先使用递归的定义来定义所有的步骤。
5 的阶乘用 5 x 4 x 3 x 2 x 1来计算。4(n-1)的阶乘用 4 x 3 x 2 x 1 来计算。 计算 n-1 的阶乘是我们计算原始问题 n!的一个子问题,因此可以像下面这样定义5的阶乘 。
- factorial(5) = 5 * factorial(4) : 我们可以用 5x4! 来计算 5!。
- factorial(5) = 5 * (4 * factorial(3!)) : 我们需要计算子问题4!,它可以用 4x3! 来计算。
- factorial(5) = 5 * 4 * (3 * factorial(2!)) : 我们需要计算子问题3!,它可以用 3x2! 来计算。
- factorial(5) = 5 * 4 * 3 * (2 * factorial(1!)) : 我们需要计算子问题2!,它可以用 2x1! 来计算。
- factorial(5) = 5 * 4 * 3 * 2 * (2 * factorial(1)) : 我们需要计算子问题1!。
- factorial(1)或factorial(0) 返回1。1! = 1,我们也可以说 1! = 1 * 0!, 0! 也等于 1。
function factorial(n) {
if(n === 1 || n === 0) { // 基线条件
return 1
}
return n * factorial(n-1) // 递归调用
}
斐波那契数列
斐波那契数列是另一个可以用递归解决的问题。它是一个由 0、1、1、2、3、5、8、13、21、34等数组成的序列。
斐波那契数列定义:
- 位置0的斐波那契数是0
- 1和2的斐波那契数是1
- n(n>2) 的斐波那契数是 (n-1) 的斐波那契数加上 (n-2) 的斐波那契数。
迭代求斐波那契数
我们也可以用迭代的方法实现了 fibonacci 函数。
function fibonacciIterative(n) {
if (n<1) return 0
if (n <= 2) rturn 1
let fibNMinus2 = 0
let fibNMinus1 = 1
let fibN = n
for (let i = 2; i <= n; i++) {
fibN = fibNMinus1 + finNMinus2
fibNMinus2 = fibNMinus1
fibNMinus1 = fibN
}
return fibN
}
递归求斐波那契数
fibonacci 函数可以这样写
function fibonacci(n) {
if(n < 1) return 0
if(n <= 2) return 1
return fibonacci(n-1) + fibonacci(n-2)
}
记忆化斐波那契数
还有第三种写 fibonacci 函数的方法,叫作记忆化。记忆化是一种保存前一个结果的值得优化技术,类似于缓存。如果我们分析在计算 fibonacci(5) 时的调用,会发现 fibonacci(3) 被计算了两次,因此可以将它的结果存储下来,这样当需要再次计算它的时候,我们就已经有它的结果了。
function fibonacciMemoization(n) {
const memo = [0,1]
const fibonacci = (n) => {
if (memo[n] != null ) return memo[n]
return memo[n] = fibonacci(n-1, memo) + fibonacci(n-2, memo)
}
return fibonacci
}
总结
本篇我们学习了怎样写两种著名算法的迭代版本和递归版本:数的阶乘和斐波那契数列。我们还学习了一种叫作记忆化的优化技术,它可以防止递归算法重复计算一个相同的值。
下一篇,我们将学习树数据结构。我们会创建一个 Tree 类,它的大部分方法会使用递归。