关于递归,算法当中基础中的基础,掌握不好,一切算法都是0.
以下是几个递归,在理解当中记忆,记录它们重在理解,不在记忆。
递归函数一定要注重返回值,具体看例子8、10。

1、多维数组打成map映射。

const buildMapDataSource = (
  dataSource,
  target = {},
  level = 1,
  parentKey = null,
) => {
  for (let i = 0; i < dataSource.length; i++) {
    const item = dataSource[i];
    let key = !parentKey ? `${i}` : `${parentKey}-${i}`;
    if (!target[item.id]) {
      target[item.id] = item;
      item.level = level;
      item.key = key;
    }
    if (Array.isArray(item?.children)) {
      buildMapDataSource(item.children, target, level + 1, key);
    }
  }
  return target;
};

2、多维数组,每项打入key,表示层级,类似key='0-0-0-1'.

buildStandardArr = (data, parentId = null, parentKey = null) => {
  const result = [];

  data.forEach((item, index) => {
    if (item.parentId === parentId) {
      const key = parentKey !== null ? `${parentKey}-${index}` : `${index}`;
      const newItem = { ...item, key };
      if (Array.isArray(item?.children) && item.children.length) {
        const children = buildStandardArr(item.children, item.id, key);
        if (Array.isArray(children) && children.length) {
          newItem.children = children;
        }
      }
      result.push(newItem);
    }
  });

  return result;
}

3、把一维打成tree多维,个人觉得这个比较难理解;

const buildMultipleArr = (data, parentId = null) => {
  const result = [];
  data.forEach(item => {
    if (item.parentId === parentId) {
      const children = buildMultipleArr(data, item.id);
      const newItem = { ...item };
      if (Array.isArray(children) && children.length) {
        newItem.children = children;
      }
      result.push(newItem);
    }
  });

  return result;
};

4、通过parentId获取parent

const getParent = (data, parentId = null, parent = null) => {
  for (let i = 0; i < data.length; i++) {
    const item = data[i];
    if (item.parentId === parentId) {
      return parent;
    }
    if (Array.isArray(item?.children) && item.children.length) {
      return getParent(item.children, parentId, item);
    }
  }
  return null;
}

5、对多维数组,内部的节点排序

const sortNodes = (data) => {
  data.sort((a, b) => a.sort - b.sort)
  data.forEach(item => {
    if (Array.isArray(item?.children) && item.children.length) {
      sortNodes(item.children);
    }
  });

  return data;
}

6、获取多维数组,某一项Item内部(有多层children嵌套)最大的层级

const getMaxLevel = (obj) => {
  let maxLevel = 0;
  function traverse(node, level) {
    if (level > maxLevel) {
      maxLevel = level;
    }
    if (Array.isArray(node.children)) {
      node.children.forEach(child => {
        traverse(child, child.level);
      });
    }
  }

  traverse(obj, obj.level);
  return maxLevel;
}

7、将多维数组打平

方法1:

 const flatArr = (data) => {
     let result = []
     data.forEach(element => {
      let temp;
       if (Array.isArray(element.children) && element.children.length) {
         temp = flatArr(element.children);
       } else {
        result.push(element);
       }
       if (temp) {
        result = result.concat(element, temp);
       }
     });
     return result;
  };

方法2:
是方法1的改写

const flatArr = (data) => {
     let result = []
     data.forEach(element => {
       result = result.concat(element, Array.isArray(element.children) && element.children.length ? flatArr(element.children) : [] )
     });
     return result;
  };

8 快排

const fastSort = (data) => {
    if (data.length <= 1) return data;
    const middleIndex = Math.floor(data.length / 2);
    const middleVal = data[middleIndex];
    const arrLeft = [];
    const arrRig = [];
    for (let i = 0; i < data.length; i++) {
      const element = data[i];
      // 这里不写 会死循环 避免自己跟自己比较
      if (i === Math.floor(data.length / 2)) {
        continue;
      }
      if (element < middleVal) {
        arrLeft.push(element)
      } else {
        arrRig.push(element)
      }
    }
    return [...fastSort(arrLeft), middleVal, ...fastSort(arrRig)]
  };

一开始并没有直接写出快排,只写出了一级非递归版
发现这种直接函数return里 还带有递归的不太好理解,比如数组打平的方法2,
我当时只写出了方法1,但自己根据方法1,写了一个方法2的变体。

9、冒泡排序
逻辑交换图,便于理解http://c.biancheng.net/view/6506.html
注意是内部for循环指针在移动,经过第一轮排序后,最后一个值一定是最大的
以此类推,经过第二轮排序后,倒数第二个是第二大的。

const sortMaoPao = (arr) => {
    for (let i = 0; i < arr.length; i++) {
      for ( let j = 0; j < arr.length - 1 - i; j++) {
        if (arr[j] > arr[j + 1]) {
          // 交换元素位置
          var temp = arr[j];
          arr[j] = arr[j + 1];
          arr[j + 1] = temp;
        }
      }
    }
    return arr;
  };

10、用递归函数 找出name=4 所在的最小子集对象

const obj = {
     name: 1,
     children: {
      name: 2,
      children: {
        name: 3,
        children: {
          name: 4,
        }
      }
     }
  };

function findObject(obj, targetName) {
    // 如果当前对象的 name 属性等于目标值,直接返回当前对象
    if (obj.name === targetName) {
      return obj;
    }
  
    // 递归遍历当前对象的 children 属性
    if (obj.children) {
      return findObject(obj.children, targetName);
    }
  
    // 如果当前对象没有 children 属性或者找不到匹配的子集对象,则返回 null
    return null;
  }

首先这句话要理解,要非常深刻的理解:

当一个递归函数在某一层的递归内部执行完 return 后,会返回到上一层的递归调用处,继续执行后续的代码

递归函数的执行可以看作是一系列的函数调用堆栈。每次递归调用时,会将当前函数的执行上下文(包括局部变量、参数值等)保存在堆栈中。当递归函数内部执行完 return 后,会从堆栈中弹出上一层的执行上下文,恢复到上一层递归的执行状态,并继续执行后续的代码。

这样的过程会一直进行,直到递归的终止条件满足,或者递归函数达到最顶层,才会结束递归调用,将最终的结果返回给最初的调用处

需要注意的是,在递归函数内部的 return 语句之后的代码可能不会执行,因为在执行 return 后,会立即返回到上一层的递归调用处

理解上面一段话后,再解释例子10的递归调用逻辑,好理解多了:
第一层函数
满足条件就返回了obj,不会走第二层递归和最后的return null。
第二层函数
满足条件进入第二层递归,满足条件就返回了obj,不会走第三层递归和最后的return null,
满足条件返回obj,相当于是第二层函数的返回值,但同时也是第一层函数的返回值,因为
在函数内部本来就返回了函数本身。
如何解释:
当一个递归函数在某一层的递归内部执行完 return 后,会返回到上一层的递归调用处,继续执行后续的代码
当返回obj后,返回上一层(第一层)递归调用处,实际就是

return findObject(obj.children, targetName)

然后第一层函数结束,递归也就结束了。

总结:
1、递归函数返回的变量,一定是在函数最前面定义的;
递归函数执行结果赋值给某一个变量,此变量定义一定是在for循环内部定义的
2、需要数组push的,都是先写递归函数,再写push追加的内容
需要返回一个object的,都是先写逻辑,再写递归函数
3、在递归函数中使用map ,forEach,注意return 并不会阻止代码往后执行,
比如例子4,如果使用forEach 永远得到的是null,会一直回到第一层函数往下走。


健儿
79 声望4 粉丝

掌握好原生js。