直接上例子,回顾下迭代和递归:
问题目标:老板吃上冰淇凌
1. 递归的方式("懒惰"的老板):
1) 老板想吃冰淇淋,仅需传达给秘书;(对于老板而言,去哪里买不需要关心,秘书可以解决
)
2) 秘书接到任务,跑去肯德基,告诉店员要一个冰淇淋;(对秘书来说,仅需传达给店员,不需要知道怎么做;店员会把想要的给他
)
3) 店员接到需求,生产出冰淇凌,交给秘书;(问题最小单元触发,终止问题分解;向上反馈
)
4) 秘书拿到了冰淇凌,用礼品盒小心翼翼的装好后,跑到公司交给老板;(秘书得到结果,经过包装后,向上反馈
)
5) 老板拿到了冰淇凌,开心的吃了起来。(老板坐在办公室一动没动,得到了想要的东西
)
每一层仅需关注从能帮自己解决问题的对接人那里得到反馈即可,直到真正解决问题的人终止问题分包,通过【传达链】向上反馈。
对应代码描述:
getIceCream = (roles, index) => {
if(index === 2) {
console.log(`${roles[index]}生产出冰淇凌!`);
return '冰淇凌';
}else {
console.log(`${roles[index]}对${roles[index+1]}说:给我冰淇凌!`);
let iceCream = getIceCream(roles, index+1);
console.log(`${roles[index]}得到了冰淇凌!`);
return iceCream;
}
}
//这里的店员生产冰淇凌即是问题终结条件:index === 2
roles = ['老板','秘书','店员'];
getIceCream(roles, 0);
//output
老板对秘书说:给我冰淇凌!
秘书对店员说:给我冰淇凌!
店员生产出冰淇凌!
秘书得到了冰淇凌!
老板得到了冰淇凌!
2. 迭代的方式(亲力亲为的老板):
1) 老板想吃冰淇淋,问了秘书怎么可以吃到;(对于老板而言,秘书应该能回到自己的问题
)
2) 秘书把老板指向肯德基店;(对于老板而言,知道了去肯德基能买到冰淇凌的线索;对秘书而言,完成了"重定向",任务就结束了
)
3) 老板对店员说,给我冰淇凌;(老板把同样的问题问了一遍店员;
相同逻辑的重复调用
)
4) 店员把冰淇凌递给老板;老板得到了想要的冰淇凌。(店员的任务就是把结果给顾客;而老板也得到了自己想要的.
达到目标,终止循环
)
对应的代码描述:
//这里的店员生产冰淇凌即是问题终结条件:index === 2
roles = ['老板','秘书','店员'];
index = 0;
while(index !== 2) {
console.log(`${roles[index]}想吃冰淇凌,从${roles[index+1]}得到解决办法`);
roles[index+1] = roles[index]; //迭代解决问题时,一般会有旧值更新的过程
index++; //根据得到的线索,推进下一步,直到问题解决(index === 2)
}
console.log(`${roles[0]}吃上了冰淇凌!`);
//output
老板想吃冰淇凌,从秘书得到解决办法
老板想吃冰淇凌,从店员得到解决办法
老板吃上了冰淇凌!
从上面的例子可以很清楚的得知:
1.递归
- 一般是使用
if else
判断方式决定处理逻辑是返回还是接着自调用; - 规模上:问题规模的分解
- 将特定条件作为最小单元,提供出口条件;
- 可读性好:分工明确,每次遍历关注点有限,无需关注太多细节;
- 占用内存,需要切换上下文;
- 冗余的计算(如斐波那契使用递归计算如果不做缓存优化的话,会有大量冗余计算)
2.迭代
- 循环结构:while、for
- 顺序上:同一类问题场景,即重复执行相同的逻辑;
- 将特定条件作为初始条件,提取在循环体外,循环体内负责通用逻辑
- 循环体内为细节逻辑,通过更新变量值,达到"重定向",不断完善以达到目标的过程;
对解决一类问题的两种思维方式。
共同点:重复计算多次
循环可以用递归代替,而一旦使用递归,就最好使用尾递归。
面试题
题目:将单向链表进行反转。假设A->B->C->D
;
先根据上面的总结进行分析:
循环体:B、C中间节点只需将指针指向前一个节点,重复的逻辑;
特殊条件:A节点需要将指针指向null;D节点指向由null转为C;
确定了关键信息,先来看下迭代和递归的实现:
递归版:
function reverseList(node){
//退出的特定条件
if(!node.next) {
return node;
}
//遍历链接
let nextNode = reverseList(node.next);
//循环逻辑
nextNode.next = node;
node.next = null;
//返回新链表的尾部指针
return node;
}
迭代版:
function reverseList(node){
//初始条件初始化
let nextNode = node.next;
node.next = null;
//循环体
while(nextNode) {
let tempNode = nextNode.next;
nextNode.next = node;
node = nextNode;
nextNode = tempNode;
}
//返回新链表的头部指针
return node;
}
虽然实现了相同的功能,但显然迭代版实现更好:
递归版:遍历链表,反向时改变节点的指向,返回新链表的尾部指针;
迭代版:只需一次遍历,就完成节点指向的改变,返回新链表的头部指针。
并不是说迭代的实现总是比递归更优解,根据实际情况而定。
compose
redux的applyMiddleware内部实现:compose([A,B,C,D])(dispatch)
将中间件组合,增强dispatch。
其中假设A、B、C、D都是中间件。对此进行分析:
预期目标:A(B(C(D(dispatch))))
;由于dispatch是延迟传入的,因此可以想到结合thunk
处理;
循环过程: 对于中间件,后一个作为前一个的参数,循环过程;
特定条件: 即中间件数组长度。
使用递归实现如下:
function compose(middlewares) {
if(middlewares.length === 1) {
return (next) => middlewares[0](next);
}
return (next) => middlewares[0](compose(middlewares.slice(1))(next));
}
另一种使用reduce的写法:
const compose = (fns) => fns.reduce((f, g) => (next) => g(f(next)));
实现原理类似,不再赘述,举这两个例子是想表达:学以致用,举一反三。
更多技术干货分享,欢迎【扫码关注】~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。