算法中常用的方法——递归

前言

我们再在上一篇文章最后提到我们如何去实现手搓一份深度拷贝的代码在我们最后的代码中有一段反在新的构造函数中复调用这个函数,其实这就是我们今天要聊的递归这一知识点,其实递归这一方法也是我们日常处理算法题经常使用的手段,现在就让我们开启递归的讲解之旅,让我们来体验递归在算法中的应用吧。

1.求阶乘

我们在求阶乘时,铜棍构造函数如何实现呢,其实我们可以通过for循环不断相乘直到乘到我们想要的值,让我们来看一下代码实现:

function jc(n){
    var i=1;
    var num = 1
    for(i=1;i<=n;i++){
        num*=i;
    }
    return num;
}

console.log(jc(5));
//输出120

我们5的阶乘为例来实现阶乘,那我们来换一个想法,我们要求5的阶乘,那5的阶乘是不是就是5乘4的阶乘,同理四的阶乘又等于三乘二的阶乘一直反复直到乘一停止,那我们是不是在每次运算时有反复调用了我们构造的jc函数,其实这就是递归思想,那我们来看一下如何通过递归实现上面思想

function jc(n){
    if(n==1){
        return 1;
    }
    return jc(n-1)*n;

}
console.log(jc(5));
//输出120

我们发现当我们使用递归的方法时,我们先要去寻找规律,找到规律后寻找一个结束的点即出口来结束递归,从而实现运算,那我们继续看一下递归在日常算法中的使用

2.斐波那契

我们知道以一个神奇的数列——斐波那契数列内部有:1 1 2 3 5 8 13 21 34 ...等一系列的数字,那我们想要实现求出第n个项的数字是多少时,应该如何通过递归实现呢?

我们通过上面对递归解题思想来看先来寻找这份数列的规律,我们可以发现从第三项开始每一项都是前两项之和,(即f(n)=f(n-1)+f(n-2))那这个规律我们找到了,那我们再去思考这个规律要通过代码实现时到哪里该去结束呢?其实不难发现当我们一直递归到有一项为1或时递归就去结束。那我们看一下上面思想代码是如何实现的

let m = 3;
function fb(n){
    if(n==1 || n==2){
        return 1;
    }
    return fb(n-1)+fb(n-2)
}

res = fb(10)
console.log(res);
//输出55

3.再聊拷贝

我们在聊深拷贝时我们留了一份自己手搓的深拷贝代码给了读者大大们今天让我们再来去看一下这份代码

let obj = {
  name: 'zyx',
  age: 18,
  sex: null,
  like: {
    n: '骑车'
  }
}

function deepClone(obj) {
  let clone = obj instanceof Array ? [] : {}
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {  
      if (obj[key] instanceof Object) {
        clone[key] = deepClone(obj[key])
      } else {
        clone[key] = obj[key]
      }
    }
  }

  return clone
}

let newObj = deepClone(obj)

console.log(newObj);

 

我们来分析一下这份代码是如何通过递归的方法来实现深度拷贝:首先我们定义一个空对象,我们最后要去返回它。然后我们要往新对象clone中添加值了。对象obj中有什么key,对象clone中也要有什么key。所以我们得去遍历对象obj。用for in 去遍历对象。这样我们就能将obj上的key复制到clone上去。但我们说过,for in遍历太强大了,它会把对象obj隐式原型上的key也遍历到,而我们不想要obj隐式原型上的key。所以在这里,我们就得判断一下obj上的key是否为显示拥有。用hasOwnProperty方法。这样我们完成的其实是浅拷贝的代码,因为当读到为对象的key时,我们也直接复制到clone上去了,而引用类型的赋值是引用地址的复制。我们在上一篇文章详细分析过。所以我们得在这里加一个判断语句,当我们读到为对象的key时,我们就得执行另外的操作。而在这一步时我们要进行类型判断了。我们知道typeof能判断原始类型的值,instanceof能判断引用类型的值。而且instanceof是通过原型链判断的。我们在这里要对引用类型进行判断,所以得用instanceof。当判断它为Object类型时,就不能直接进行复制了,得做另外操作。那应该怎么写呢?我们这样想一想:当我们读到为对象的key时,这个对象里面是不是又会有key,这些key可能为原始类型,可能为引用类型。我们需要将这些key复制到新对象中所对应的对象中去。当碰到原始类型时,直接复制;当碰到引用类型时,需要干其它操作。那我们写的这个deepClone方法不就是干这个的吗?

当我们读到为对象的key时,我们就把这个对象传到我们写的这个deepClone方法中去,这个方法就会帮我们把原始类型复制过去,当再次碰到引用类型时,再将这个引用类型传到deepClone方法中去。总有一天会碰到一个对象里只有原始类型。这样就找到出口,不会无穷无尽执行下去。当碰到为对象的key时,我们就将它传到deepClone(obj[key])中去,让这个方法帮我们去进行这个对象的拷贝。如果这个对象里面还有对象的话,这个对象的对象也会被传到deepClone(obj[key])中去进行拷贝。

这样我们就完成了深拷贝的代码。当然这里还有个小细节,因为我们要深拷贝的可能是数组。所以我们再加一条判断语句判断obj是对象还是数组。这样我们就实现了真正的深拷贝

4.数组降维

我们来看递归实用在另一道算法题之中,如果我们想对let arr = [1, 2, [3, [4]]]进行降维输出[1, 2, 3, 4]我们先来看一下代码是如何实现的

let arr = [1, 2, [3, [4]]]


function flattenArray(arr) {
      let newArr = []
      for (let key in arr) {
        if (Array.isArray(arr[key])) {
          //newArr.push(...flattenArray(arr[key]))
          newArr = newArr.concat(flattenArray(arr[key]))// concat() 会返回一个新数组
        } else {
          newArr.push(arr[key])
        }
      }
      return newArr
    }
    console.log(flattenArray(arr));


console.log(flattenArray(arr));

我们想要将原数组进行降维,那么我们要先构造一个新的数组来存放降维后的数组并进行返回,我们把准备阶段做好后,现在就要实现降维操作,首先我们先要去遍历(通过for key in arr)的方法来实现遍历,当我们在对这个数组遍历的过程中如果遍历的是原始类型数据直接push进新的数组中那如果遍历的而还是一个引用类型(在本题中就是引用类型为数组)就要在继续开始原方法来实现遍历第二层的数组内的原始类型数据就反复进行不断地对数组被剥离原始类型数据放入新数组中,而当我们发现当我们遍历到原数组没有值也就是已经遍历结束,那就说明递归结束,通过这个思想来实行使用递归方法将数组降维。

结语

当我们在以后遇见要使用递归地算法题是时,我们就要想去找到题目的规律,同时再去思考当我们要在什么情况下去结束递归。

原文:https://juejin.cn/post/7445155431139377189

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值