递归之美,在于你会
tips:每个技术点都值得优学优写:6期
什么是递归
Mozilla 上这样解释递归(Recursion)这个术语:
一种函数调用自身的操作。递归被用于处理包含有更小的子问题的一类问题。 一个递归函数可以接受两个输入参数:一个最终状态(终止递归)或一个递归状态(继续递归)。
我的理解是:递归就是有条件的自己调用自己。 所谓有条件就是可以终止,不会出现死循环, 符合条件继续自己调用自己,不符合调用就到此为止。
递归应用场景
tips:学递归,要知道递归的用法,以及递归的应用场景。 学会递归的标志之一,是遇到适合的场景能想起来该用递归了。
数据:处理一些具有层级递进特征的数据,例如 tree 数据,有时会比较方便。
数学: 8皇后问题,汉诺塔,阶乘问题,迷宫问题。
算法:比如快排,归并排序,二分查找,分治算法等。
递归应用与反扁平化示例
tips:我对递归好很有好感,因为好用,物超所值。 在处理有层级的,每个层级的结构相同类似的数据时,非常棒。下面将举几个例子。
递归应用示例:tree 数据扁平化
先看下递归应用前后,扁平化数据前后的效果图
部分关键代码
/** *将 tree 数据扁平化 * @param tree 需要被扁平化的具有层级结构的 tree 数据 * @param newList 接收扁平数据的数组 */ function treeTranslate (tree, newList) { // (tips:Mozilla上关于递归的这个术语,解释说一个递归函数可以接受两个输入参数:一个最终状态(终止递归)或一个递归状态(继续递归)。) // 从两个入参的角度看,newList.length>10000 为真可以理解为“最终状态”。 // 这个条件设置无关业务,是出于极限保护设置的,可不设。因为假设这个 tree 数据扁平化后不可能超过 100000, // 一旦超过说明出错了,所以做出了这个极限保护条件。 if (newList.length > 100000) { return } tree.map(e => { newList.push(e) // 自己调用自己,条件是 e.children.length 为真 // 从两个入参的角度看,e.children.length 为真可以理解为“递归状态”。 if (e.children && e.children.length) { treeTranslate(e.children, newList) } }) }
反扁平化示例:将扁平数据转换为 tree 数据
先看下反扁平化前后效果图
部分关键代码
/** * 反扁平化:将扁平数据转换为 tree 数据 * @param list 扁平数据 */ function toTree (list) { let tree = [] list.map(e => { // 以 e.pid===null,作为判断是否根节点的依据,或者直接写死根节点(如果确定的话), // 具体以什么为根节点的依据得看数据的设计规则,通常是判断层级或是否根节点的标记 if (e.pid === null) { tree.push(e) } list.map(e2 => { if (e2.pid === e.id) { // 避免出现重复数据 const index = e.children.findIndex(sub => sub.id === e2.id) if (index === -1) { e.children.push(e2) } } }) }) console.log("反扁平化后:",tree) }
完整 demo 示例
<!DOCTYPE html><html lang="en"><head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <title>递归(Recursion) Demo</title></head><body><h1>递归(Recursion) Demo</h1><script> window.onload = function () { init() } function init () { let newList = [] console.log('扁平化前:', treeList) treeTranslate(treeList, newList) console.log('扁平化后:', newList) // 反扁平化 toTree(JSON.parse(JSON.stringify(newList))) } const treeList = [ { id: 1, pid: null, label: '一级', value: '1', flag: true, children: [ { id: 2, pid: 1, label: '二级1', value: '2.1', flag: false, children: [] }, { id: 3, pid: 1, label: '二级2', value: '2.2', flag: true, children: [] }, { id: 4, pid: 1, label: '二级3', value: '2.3', flag: true, children: [ { id: 5, pid: 4, label: '三级1', value: '3.1', flag: true, children: [] }, { id: 6, pid: 4, label: '三级2', value: '3.2', flag: true, children: [] }, ] }, ] } ] /** *将 tree 数据扁平化 * @param tree 需要被扁平化的具有层级结构的 tree 数据 * @param newList 接收扁平数据的数组 */ function treeTranslate (tree, newList) { // (tips:Mozilla上关于递归的这个术语,解释说一个递归函数可以接受两个输入参数:一个最终状态(终止递归)或一个递归状态(继续递归)。) // 从两个入参的角度看,newList.length>10000 为真可以理解为“最终状态”。 // 这个条件设置无关业务,是出于极限保护设置的,可不设。因为假设这个 tree 数据扁平化后不可能超过 100000, // 一旦超过说明出错了,所以做出了这个极限保护条件。 if (newList.length > 100000) { return } tree.map(e => { newList.push(e) // 自己调用自己,条件是 e.children.length 为真 // 从两个入参的角度看,e.children.length 为真可以理解为“递归状态”。 if (e.children && e.children.length) { treeTranslate(e.children, newList) } }) } /** * 反扁平化:将扁平数据转换为 tree 数据 * @param list 扁平数据 */ function toTree (list) { let tree = [] list.map(e => { // 以 e.pid===null,作为判断是否根节点的依据,或者直接写死根节点(如果确定的话), // 具体以什么为根节点的依据得看数据的设计规则,通常是判断层级或是否根节点的标记 if (e.pid === null) { tree.push(e) } list.map(e2 => { if (e2.pid === e.id) { // 避免出现重复数据 const index = e.children.findIndex(sub => sub.id === e2.id) if (index === -1) { e.children.push(e2) } } }) }) console.log("反扁平化后:",tree) }</script></body></html>原文:https://juejin.cn/post/7096812307304415240