js递归

8,108 阅读3分钟

1.什么是递归

递归是一把双刃剑,它是一种自己调用自己的算法,关键在于找到它的出口和入口,否则将导致导致堆栈溢出,陷入一种死循环的状况。许多 FP 语言不使用循环;他们只使用递归。递归的思想在于将一个未知的问题转变成一个已经解决的问题来实现。

2.递归的关键

  • 将递归函数写好
  • 寻找递推关系
  • 找到递归出口条件
  • 根据条件和关系形成递推函数

3.头递归和尾递归

如果递归有在函数调用后要执行的代码语句,则它是头递归。头递归通常很难转换为循环语句
        function head(n){
            if(n==0){
                return 0
            }
            head(n-1)
            console.log(n);
        }

尾递归在函数调用之后不会有任何代码语句,通常在函数声明的末尾。尾递归很容易转换成循环语句。
 function last(n){
            if(n==0){
                return 0
            }
            console.log(n);
            last(n-1)

        }

一般来说,尾递归会好一些。尽管它们都具有相同的时间复杂度和空间复杂度,但尾递归在函数堆栈中的内存方面更具优势。头递归将在函数堆栈内存中等待,直到执行后才会递归代码语句,这会导致执行结果整体延迟,而尾递归则直接在函数堆栈中终止执行。

4.常见的递归操作

4.1 求阶乘

//从自身往前递推并将所递推的数相乘,结果用于下一次计算
        function jiecheng(num){
            //找到出口
            if(num<=1){
                return 1
            }
            else{
                return num*jiecheng(num-1)
            }
        }

        console.log(jiecheng(4));

4.2 递归实现多层对象嵌套的深拷贝

     const deepClone=(obj)=>{
            let temp={}
            //遍历对象,判断对象中属性值是否是对象,如果是重复操作
            for(let k in obj){
                if(typeof obj[k]=='object'){
                   temp[k]=deepClone(obj[k]);
                }
                else{
                    temp[k]=obj[k]
                }
            }
            return temp
        }

4.3 树递归遍历取值

要求:获取角色下所有三级权限的id

image.png

 getLeafkey(node,arr){
     if(!node.children){
       return arr.push(node.id)
     }
     node.children.forEach(item=>{
       this. getLeafkey(item,arr)
     })

   }
//这里的node表示所有的节点,如果该节点上没有children属性,就表示已经到达三级节点,如果存在则继续遍历数组,并递归这一级

4.4 递归实现扁平化

需求描述: const o = { a: 1, b: [1, 2, { c: true }], d: { e: 2, f: 3 }, g: null };fn(o) => 扁平化转换{ "a": 1, "b[0]": 1, "b[1]": 2, "b[2].c": true, "d.e": 2, ... }

const o = { a: 1, b: [1, 2, { c: true }], d: { e: 2, f: 3 }, g: null };
const fn = (root) => {
  const obj = {};
  const f1 = (root, propName) => {
    //循环对象,并迭代成数组,键和值形成数组,并存放在一个大数组内
    for (let [key, value] of Object.entries(root)) {
      //判断这个值是否存在并类型是否是一个对象
      if (value && typeof value === "object") {
        //是一个对象,则把这个对象递归上面一步,将这个对象在进行调用传值,
        //根据对象中得值是否是一个数组判断proName是否存在,
        //如果存在,将该变量跟当前得对象一起传入函数进行递归
        f1(value, propName ? `${propName}[${key}]` : key);
      } else {
        //这里proName存在,此时得root应该是个数组
        if (propName) {
          if (Array.isArray(root)) {
            //这里相当于b[0]:1,b[1]:2
            obj[`${propName}[${key}]`] = value;
          } else {
            obj[`${propName}.${key}`] = value;
          }
        } else {
          //这里相当于实现"b[2].c": true
          obj[key] = value;
        }
      }
    }
  };
  f1(root);
  return obj;
};

console.log(fn(o));

4.5 递归实现斐波那契数列

斐波那契数列是一系列数字:0、1、1、2、3、5、8、13、21、34 等等。该模式涉及将前两个数字相加,因此 0 + 1 = 1、1 + 1 = 2、1 + 2 = 3、2 + 3 = 5 等。换句话说,位置n(for n > 2) 处的斐波那契数是斐波那契数的(n - 1)加上斐波那契的(n - 2)

image.png

        function fibonacciSequence(n) {
        //先定义第一项和第二项
            const memo = [0, 1];
            const fib = (n) => {
            //当数组到
                if (memo[n] != null) return memo[n]; 
                //后面一个数等于数组前两项之和
                return memo[n] = fib(n - 1, memo) + fib(n - 2, memo);
            };
            return fib(n);
        }

4.6 递归计算数组索引

需求:给定一个数字数组,找到第一个负值的索引

        let sals = [134.56, 562.22, 357.81, -303.73, 256.45, -453.78];
        const debitIndex = (data, index) => {
            if (index === data.length) {
                 return false
                 }
            else if (data[index] < 0) {
                return index
            }
            else{
                 return debitIndex(data, index + 1)
                };

        };

        let result = debitIndex(sals, 0);