问题描述
题目是对一个对象中的value属性做遍历求和, 对象结构如下:
var nodes = {
value: 1,
children: [
{
value:2,
children:[
{
value:4,
children:[...]
},
{
value:3,
children:[...]
},
...
]
},
{
value:5,
children:[...]
},
...
]
}
我的答案
function sum(node){
if(node.children.length===0){
return node.value
}
else{
return node.children.reduce(
(prev, curr) => prev + sum(curr),
node.value
)
}
}
sum(nodes)
用DFS和BFS求解
首先说一下
BFS 广度优先搜索算法
和DFS 深度优先搜索算法
。此题的两种
搜索算法
分别实现如下:BFS
DFS
区别
路径
递归下去,回溯上来
,这就是DFS
的简单逻辑。而BFS
则是层层递进
的逻辑。所以如果结果是一个集合,那么集合中项的顺序是不同的。
此例中,结果是唯一求和值,所以2种算法对结果并无影响。
性能
由于路径的不同,将导致求和顺序的不同。但是,不管哪种算法,
所经历项的总数是相同的
。我们常常能看到一个普遍的结论是,
BFS
的性能要优于DFS
。为什么会有这个结论呢?首先,需要明白的是这2种算法的第3个字母
S
表示的是什么意思。S
代表的是Search
,所以这2种算法是针对搜索
而言的,而搜索是无须遍历每一项的
。如果数据离散度
较高,那么使用BFS
更能容易命中,而实际生活种,数据总是离散
的,所以往往BFS
要比DFS
更快。可是,如果只是更快,未必就是更好。而且考虑到遍历所有数据样本的情况,为什么仍然会有
BFS
优于DFS
的结论呢?要说这个,首先看一下这2种算法的实现。
DFS
是递归的方法,性能开销在于函数调用栈的出入栈
;BFS
是队列的方法,性能开销在于内存申请,队列的出入
。这是两者本质的区别。在不考虑内存限制的情况下,内存的操作会略快一点,但要视不同语言而定
,但两者的差距并没有想象中的那么大。而在内存限制的情况下,其实BFS
要增加额外的内存管理成本,反而得不偿失。另一点在于,
BFS
因为有着第三者队列
的存在,所以在多进程
模型下,可以将队列分片
,交由不同的进程处理。而DFS
由于不知其深度,无法很好地平均分配,即使分片了,也仍然存在DFS
的问题。因此,结论往往是
BFS
要优于DFS
。然而Javascript不一样!
虽然Javascript有办法做到多进程和多线程,但是其开销比静态语言要大的多。因为
VM
的执行机制,只有当其翻译到fork
新进程的时候才会去优化代码和执行,所以会慢很多。而在浏览器端,虽然可以利于Worker
做到多进程,但是其创建和通信的成本仍然巨大。所以就目前而言,BFS
的多进程优势对于Javascript并不适用。在此题中,因为结果是需要
唯一确定的值
,而且必须经历所有节点
,所以BFS
和DFS
的性能差异其实就是2种算法的性能开销的博弈。在ES6+之前,每次调用函数必须为函数创建额外的执行上下文
,由于执行上下文
是在函数执行时动态赋予的,所以在函数申明和定义时无法优化;但是ES6的箭头函数
在函数定义时指定了上下文
,因此避免了额外的开销。另外,ES6还有一个特性是尾调用优化
(虽然目前只有Safari支持),但是尾调用优化
的本质就是VM
内部自动优化,避免了DFS
的出入栈
问题。所以,对于Javascript更快。不过,
实践是检验的唯一标准
,上面的都是理论,实际结果比理论更重要。我在自己电脑上跑了一下,此题的
DFS
解法比BFS
解法,在Chrome下要快1倍,在Safari下要快40%(看来V8还是厉害)。你也可以自己试验一下。不要人云亦云,
实践出真知
,亲身实践比任何理论都有说服力。而且技术是不断进步的,如果理论只是维持不变,或者片面,总会被淘汰的。最后,给些类递归的新奇解法吧。
Proxy
类似的,可以用ES5的
getter
实现。JSON
解决问题是唯一目标
我在面试时,往往会遇到一些令人哭笑不得但又确实很有效的解答。但只要解决问题就好啦,最实用的才是最好的。
就此题而言,可以看出,其
value
不管是在多深的层级,其实都是连续递增
的。所以单就此题而言,完全可以变成求从1
到最大的value值
的和。所以,只要找到最大的value值
,就可以计算了,而不必遍历所有节点。