举个例子, 一组小朋友排了一个纵队, 类似这样
‘张三’ -> ‘李四’ -> ‘王五’ -> ‘小明’ -> ‘小花’ -> ‘小黑’ ;
假如队列报数, 从张三
开始, 很容易就能得出队伍的长度, 这是一个累加的过程.
现在又假如, 直接问小黑
, 现在队伍里有多少人, 当然可以从小黑
处报数再统计.
但还有一种方法就是:
小黑
问他前面的人小花
: ” 嘿, 哥们, 你前头有多少人啦?” ,
小花
也不清楚, 只能继续问小明
: ” 嘿, 哥们, 你前头有多少人啊? “,
直到李四
问张三
: : ” 嘿, 哥们, 你前头有多少人啊? ”
张三
告诉李四
: “我前面 0 个人” ,
李四
再告诉王五
: “我前面0+1 个人”,
王五
再告诉小明
: ‘我前面1+1个人” ,
小明
再告诉小花
: ‘我前面2+1个人” ,
小花
再告诉小黑
: ‘我前面3+1个人” ,
小黑
掐指一算 , 我前面就是 4 + 1 个人. 再加上我自己, 就是总共6个人啦.
概念
大问题的解, 可以通过 n 次 求解得出结论. 大问题的解依赖小问题的解, 小问题的解又依赖小小问题的解, 以此类推 , 每一次缩小问题, 都需要等待再小问题的解决 , 直到一个问题足够小, 有确定解返回.
递归的几大要素
- 可期的终止条件 , 比如
张三
前面没有人为0 - 相同的求解方法 , 比如 都是 相同的询问 , ” 嘿, 哥们, 你前头有多少人啊? “
- 小问题的解, 通过 相同的解法, 得到大问题的解. 比如 0+1 , 1+1 , 都是前面的人返回 + 1
抽象成递归基本套路
分析
- 知道什么, 即终止条件?
- 可以知道什么?
- 求什么?
- 找到缩减问题的准则
- 缩减前后两者的关系
- 抽象通用方法
代码
- 先写一个函数 ( a )
- if( 终止条件 ) { return 终止结果}
- 函数 ( a ) 体内 return 这个函数 ( a )
- return 这个函数 ( a ) 时根据 递减问题准则和前后两者关系 做调整, 函数的参数为递减的问题的指标, 比如 天, 比如数字递减.
示例
- 求 n 阶乘 ?
缩减问题的准则: 减 1
缩减前后两者的关系: n! = n* (n-1)!
抽象通用方法: fact(n) = n* fact(n-1)
找到终止条件: fact(1) = 1
// step1 先写一个函数
function fact(){}
// step2 if( 终止条件 ) { return 终止结果}
function fact(n){
if(n===1){
return 1;
}
}
//step3 函数 ( a ) 体内 return 这个函数 ( a )
function fact(n){
if(n===1){
return 1;
}
return fact();
}
// step4 return 这个函数 ( a ) 时根据 递减问题准则和前后两者关系 做调整
function fact(n){
if(n===1){
return 1;
}
return n*fact(n-1);
}
- 一个猴子摘了n个桃子, 第一天吃了 n/2 +1 个桃 , 第二天吃了上一天的 n/2 + 1 个桃 , 直到最后一天 ( 第9天 ) 吃到剩下1个桃. 那么他总共摘了多少个桃 ?
分析
- 知道什么? 终止条件 => 最后一天剩 1 个桃
- 可以知道什么? 倒数第二天开始有多少个桃 => x - (x/2+1) = 1 , 倒数第二天共 (1+1) *2 个 , 括号中 的 第一个 1 为后面一天的初始个数 , 第二个 1 为常数 ;
- 求什么? 第一天有多少个桃.
- 缩减规则 => 天递减
- 前后者关系 => 前面一天的桃 等于 (今天的桃+1) * 2
- 终止条件 => 最后一天 1 个桃
// step1 先写一个函数
function getPeachNum(day) {
}
// step2 if 终止
function getPeachNum(day) {
if(day===9){
return 1;
}
}
// step3 函数体内返回这个函数
function getPeachNum(day) {
if(day===9){
return 1;
}
return getPeachNum(day);
}
// return 这个函数 ( a ) 时根据 递减问题准则和前后两者关系 做调整
// 前面一天的桃 等于 (今天的桃+1) * 2
function getPeachNum(day) {
if(day===9){
return 1;
}
return (getPeachNum(day+1)+1)*2;
}
getPeachNum(1); // 得第一天的桃子 共 766 个
- 树形结构
这里结束的条件 , 是循环的结束,即结束.
// 原数据
var originData = [
{ title: '标题1', id: '1', pid: '0' },
{ title: '标题1-1', id: '1-1', pid: '1' },
{ title: '标题1-2', id: '1-2', pid: '1' },
{ title: '标题2', id: '2', pid: '0' },
{ title: '标题2-1', id: '2-1', pid: '2' },
{ title: '标题2-2', id: '2-2', pid: '2' },
{ title: '标题2-1-1', id: '2-1-1', pid: '2-1' },
{ title: '标题2-2-1', id: '2-2-1', pid: '2-2' },
{ title: '标题2-2-2', id: '2-2-2', pid: '2-2' },
{ title: '标题2-2-2-1', id: '2-2-2-1', pid: '2-2-2' },
{ title: '标题2-2-2-2', id: '2-2-2-2', pid: '2-2-2' },
];
// 转成树形结构
function toTree(arr, pid,linkKey='children') {
var treeArr = [];
var allTreeLeaf = arr.filter(item => item.pid === pid);
allTreeLeaf.forEach(tree => {
let _children = toTree(arr, tree.id,linkKey)
if (_children.length) {
tree[linkKey]= _children;
}
treeArr.push(tree);
})
return treeArr;
}
var formatTree = toTree(originData,'0');
// 将树形结构展平还原回去
function flattenTree(treeData, linkKey) {
var result = [];
treeData.forEach(thrunk => {
if (thrunk[linkKey] && thrunk[linkKey].length) {
result = result.concat(flattenTree(thrunk.children, linkKey))
}
delete thrunk[linkKey]
result = result.concat(thrunk);
})
return result;
}
console.log(flattenTree(formatTree, 'children'))