算法----递归

215 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

一、如何理解递归

递归,是指在函数的定义中使用函数自身的方法,或者在自己函数调用的下级函数中调用自己

简单的举个例子:

function test(someParam) {
    test(someParam);
}

// 自己函数调用的下级函数中调用自己
function test1(someParam) {
    test2(someParam);
}
function test2(someParam) {
    test1(someParam);
}

上面的代码只是简单的举个例子,描述递归的大概使用方法,在实际使用过程中,每个递归函数都必须有基线条件,即一个不再递归调用的条件(停止点),以防止无限递归

递归函数考虑的要点

  1. 确认函数想做什么,即函数的功能。
  2. 寻找递归的结束条件,防止无限递归
  3. 找出函数的等价关系式

接下来我们通过不同的题目来理解递归算法

二 、 递归应用

2.1 计算一个数的阶乘

定义数 n 的阶乘,定义为 n!,表示 从 1 到 n 的整数的乘积。

例:5 的阶乘表示为 5!,和 5 × 4 × 3 × 2 × 1 相等,结果是 120

我们都知道,阶乘的算法是(n) * (n - 1) * (n - 2) * (n - 3) * ... * 1 那么我们可以得出一个公式f(n) = n * f(n-1), 终止条件:n==1时,返回1

// 计算一个数的阶乘
function factorial(n) {
    if (n == 1 || n == 0) return 1
    return n * factorial(n - 1)
}
console.log(factorial(5)) // 120

上面我们就根据递归,简单实现了一个数的阶乘

2.2 斐波那契数列

斐波那契数列:是数学家斐波那契以研究兔子繁殖为例研究的数列,故称“兔子数列”,又称为黄金分割数列。

例子:0、1、1、2、3、5、8、13、21、 34 等数组成的序列,数 2 由 1 + 1 得到,数 3 由 1 + 2 得到,数 5 由 2 + 3 得到。以此类推,我们可以得到一个公式: F(0)=0 F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2) (n>=3,n∈N*)

这样的话,我们可以得出递归如何求取斐波那契数列

//斐波那契数
function fibonacci(n) {
    if (n < 1) return 0;
    if (n <= 2) return 1
    return fibonacci(n - 1) + fibonacci(n - 2)
}

console.log(fibonacci(5)) //5

若有看不明白的,可以查看下面的调用结果图,加深理解

image.png

针对上面的斐波那契数列,我们还有一种优化的办法。在执行代码的过程中,保存前一个结果的值来进行优化

function fibonacci(n) {
    let last1 = 1, last2 = 1, temp
    for (let i = 3; i <= n; i++) {
        temp = last1 + last2
        last1 = last2
        last2 = temp
    }
    return last2
}

2.3 深度copy

其实们在写深度copy的时候,也会用到递归,因为对应或者数组不知道有多少层,故需要递归去执行

function deepClone(obj, hash = new WeakMap()) {
  if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  // 可能是对象或者普通的值  如果是函数的话是不需要深拷贝
  if (typeof obj !== "object") return obj;
  // 是对象的话就要进行深拷贝
  if (hash.get(obj)) return hash.get(obj);
  let cloneObj = new obj.constructor();
  // 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
  hash.set(obj, cloneObj);
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      // 实现一个递归拷贝
      cloneObj[key] = deepClone(obj[key], hash);
    }
  }
  return cloneObj;
}

2.4 经典走台阶问题

问题:某人需要走上10级台阶,有两种走法,走法A:一步1个台阶;走法B:一步2个台阶。两种走法可以任意交替使用,问走上10级台阶共有多少种方法?

终止条件:

  1. 层台阶时:走法只有一种(1步一台阶),即n==1 时,return 1
  2. 层台阶时:走法有2种(2次1步1台阶 或 1步2台阶) n == 2 时,return 2

则上到第10层台阶有两种办法:

  1. 从第8层 --> 第10层 (1步2台阶)
  2. 从第9层 --> 第10层 (1步1台阶)

由上述方法类推,我们可以得出对应的递归函数

function fn(n) {
    if (n === 1 || n === 2) {
        return n;
    }
    return fun(n - 1) + fun(n - 2);
}