JavaScript递归思想,JS特别实用的方法

227 阅读4分钟

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

递归思想

什么是递归

递归是一个函数,然后再在函数里面调用自己来达到一个循环
"从前有座山,山里有座庙,庙里有个老和尚再给小和尚讲故事,讲的是从前有座山"
这一句大家都有印象吧,其实这也是一个递归,因为他一直在循环,但是它是个死递归,因为它没有停止的条件

function fn() {
    console.log(1);
    fn()
}
fn()

一直重复调用自己,没有停止条件,这个时候打开就会卡死,一直输出1停不下来 image.png 这时候就需要给他加上一个终止的条件

let num = 0;
function fn() {
    console.log(1);
    num++;
    if (num === 100) return;
    fn();
}
fn();

当循环100次之后终止循环

递归的优缺点

计算机其实不是很聪明,只能执行一些简单的,但是他又特别快,可以将复杂的问题拆解成许多简单的问题再去解决
递归通过重复调用自身,将一些复杂的问题简单化
但是,递归它特别消耗计算机的性能,容易导致堆栈溢出,就会报错
这个时候用递归将问题简单化之后,可以用for去达成一样的效果,就可以解决性能消耗过多的问题

示例

我们先来做几个简单的例子

计算1+2+3+...+n

数学的计算公式是 (n+1) n/2
(100+1)*100/2=5050
(666+1)*666/2=222111
(8888+1)*8888/2=39502716
用递归做的方法就是n + fn(n - 1)

function fn(n) {
    if (n === 1) return n
    return n + fn(n - 1);
}
console.log(fn(100)); // 5050
console.log(fn(666)); // 222111
console.log(fn(8888)); // 39502716

这个递归不能计算太大的数,我算了下我电脑大概能算10444,再大就报错了,不过应该没有多少人会这么无聊算弄这个吧,现在我们将它将它转换成for

let sum = 0;
for (let i = 1; i <= n; i++) {
    sum += i;
}

理论上可以计算不管多大的数,前提你的电脑足够好

计算 n 的阶乘

数学公式我就不写了,我估计大伙都知道吧
用递归就是 n * fn(n - 1)

function fn(n) {
    if (n === 1) return n;
    return n * fn(n - 1)
}
console.log(fn(6)); // 720
console.log(fn(10)); // 362008
console.log(fn(20)); // 2432902008176640000

// for
for(let i = 1; i <= n; i++){
    sum *= i;
}

斐波那契数列

斐波那契数列的规律:从第三个数开始每个数都是前两个数的和
1 1 2 3 5 8 13 21 34
将他们的规律转换为递归就是

function fn(n) {
    if (n === 1 || n === 0) return n;
    return fn(n - 1) + fn(n - 2); 
    // 它是从最后一个数开始算的 左边这个是上一个数 右边这个是上上一个
    // 比如算的第8个数列 那就是21 fn(n-1)算的就是13 fn(n-2)就是8 这样一直算下去直到n===1||0的时候终止
}

for循环

let sum = 0;
let A = 1; // 上上一个数
let B = 1; // 上一个数
// i = 3 从第三个数字开始算 应为规律就是从第三个数字开始每个数都是前两个的之和
for (let i = 3; i <= n; i++) {
    sum4 = A + B;
    A = B;
    B = sum;
}

10个台阶 一次走两阶和一次走一阶两种走法 上去有几种走法

我们先从最后的开始,距离10阶只差一步的时候有两种可能
1.距离10还有2阶
2.距离10还有1阶
根据这两种走法我们可以得出 10阶=8阶+9阶,这个是不是和斐波那有异曲同工之妙?
都是等于前两个数的和,所以我们可以用斐波那契数列的方法来算,只不过它只有两种走法
走1和走2,所以它前两个数只能为 1和2
根据以上写出递归算法

function fn(n) {
    if (n === 1 || n === 2) return n;
    return fn(n - 1) + fn(n - 2);
}
// for
let A3 = 1; // 走法A
let B3 = 2; // 走法B
let sum3 = 0;
for (let i = 3; i <= n; i++) {
    sum3 = A3 + B3;
    A3 = B3;
    B3 = sum3;
}

QQ图片20221124152948.png

生成一组不重样的随机数

在使用js的时候,有时需要一组不重样的一组数,做的时候没有思路,这时候可以想到递归

let randomArr = [];

function fn(n) {
    // 这里就算 10000 以内不重复的数
    randomArr.push(~~(Math.random() * 10000));
    // 当数组的元素个数等于n的时候 开始数组去重
    if (randomArr.length === n) {
        let obj = {};
        randomArr.forEach(item => obj[item] = item);
        randomArr = Object.values(obj)
    }
    // 经过去重之后如果数量还是达到了要求就终止递归
    if (randomArr.length === n) {
        return;
    }
    fn(n);
}

用递归特别的消耗性能,优化的话根据递归将它再转换为for循环
for循环的话里面的内容都是差不多的,只需要在它去重之后计算距离要求个数还有多少,在加上多少次循环就ok了

let n = 666666; // 随机个数
let randomArr = [];
for (let i = 1; i <= n; i++) {
    // 这里计算的是 1000000 以内的随机数
    randomArr.push(~~(Math.random() * 1000000));
    if (randomArr.length === n) {
        let obj = {};
        randomArr.forEach(item => obj[item] = item);
        randomArr = Object.values(obj);
        // 计算还差多少 就加上多少次循环
        n += n - randomArr.length;
    }
}
console.log(randomArr);

QQ图片20221124155905.png

总结

虽然递归特别消耗性能,但是可以利用递归将简化后的算法转换成for来优化
使用 递归思想 将抽象的问题给简化,在利用for将递归的性能消耗问题解决掉