继上次 递归实现列表赋值 的经历之后,今天又遇到了新的需要:给你一棵树的最深节点value值,让你找到从根到该节点的value列表。树的结构长下面这样
{
value: 1,
children: [
{
value: 2,
children: [
{
value: 3
},
{
value: 4
}
]
},
{
value: 5,
children: [
{
value: 6
},
{
value: 7
}
]
}
]
}
如果没听明白的话,你就把它想象成一个级联框,给定最后一级value值,返回从第一级到最后一级的列表。就拿上面的树结构举例,我简单画了个级联框
如果 value
是 3 或 4 的话,返回的 value列表
应该是 [1, 2, 3]
或 [1, 2, 4]
;
如果是 value
是 6 或 7 的话,返回的 value
列表应该是 [1, 5, 6]
或 [1, 5, 7]
。
当然,有的可能只有两级级联,所以 value
也可能是 2 或者 5 ,那么返回的 value
列表则是 [1, 2]
或 [1, 5]
。
好,问题分析完了,下面想办法解决。因为之前用过 递归实现列表赋值 ,为了图省事,顺便再练习一下遍历算法,那就敲定递归!直接上代码
// 1. 创建数组valueList作为栈
let valueList = []
let targetValue = 6
function traversalTree(tree) {
tree.map((item) => {
// 2. 当value === targetValue,返回栈
if (item.value === targetValue) {
valueList.push(item.value)
return valueList
}
// 3. 如果有children属性,把key入栈,访问children
if (item.children) {
valueList.push(item.value)
traversalTree(item.children)
}
// 4. 没有children属性,跳到上一级
// 注意!这里不是清空栈! valueList = []
valueList.pop()
})
}
很顺利啊,那看看函数返回的结果是什么。我去,怎么拿到的值是 undefined
?查了查资料,发现里头的 return valueList
语句并没有真正退出 traversalTree
函数。当语句取到我们需要的 vlaue
时,可能已经在第二级或第三级级联框中,也就是在第二层或第三层堆栈中,return
只是返回到上一层堆栈(第一层或第二层),继续执行未完成的语句。
好家伙,这么一说还得多加个 return
才行。那万一嵌套太深了可咋整?嵌套多了那不得……(脑补一下地狱回调的代码
换个思路
既然简单的 return
行不通,那就换条路走。百度了一下发现还有种方法 —— try...catch
。大体思路就是把遍历的操作放在 try
中,当遇到符合条件的情况时主动 throw
一个错误,用 catch
捕获,这样就能跳出嵌套了。因为整个递归算法都在 try
语句中,所以退出 try
语句后,也就退出了递归算法(真的是这样吗?🤔)
function traversalTree(tree) {
try {
tree.map((item) => {
// 2. 当value === targetValue,返回栈
if (item.value === targetValue) {
valueList.push(item.value)
throw valueList
}
// 3. 如果有children属性,把key入栈,访问children
if (item.children) {
valueList.push(item.value)
traversalTree(item.children)
}
// 4. 没有children属性,跳到上一级
valueList.pop()
})
} catch (valueList) {
return valueList
}
}
又跑了一遍代码,函数返回值竟然还是 undefined
。纳闷了,咋又失败了呢?于是打了个断点去查了查堆栈
在主动 throw
(找到值)后,从堆栈中可以看到,如果处理完 catch
中的语句,代码还是会回到堆栈处理没调用完的函数。也就是说,简单地使用 try...catch
是不起作用的。
接着想办法
到这里我有点明白过来了,try...catch
可以提前终止循环,但不能跳出堆栈,他只是申请回到上一次调用,和 return
功能一致。如果想要跳出这么多的堆栈,还得加一个判断,来一次一次地跳出去。对于我们的算法来说,需要有一个 flag
变量判断是否已经找到 targetValue
,并且在每一次递归调用去做判断。
function getValueList(targetValue) {
// 1. 创建数组valueList作为栈
var valueList = []
let flag = false
const that = this
// 这里的that主要是传递this,还有一种this绑定的方法我忘了,就先凑合着吧...
traversalTree(tree, that)
return valueList
function traversalTree(tree, that) {
tree.map((item, index, arr) => {
if (flag) return
// 2. 当value === targetValue,返回栈
if (item.value === targetValue) {
valueList.push(item.value)
flag = true
return
}
// 3. 如果有children属性,把key入栈,访问children
if (item.children) {
valueList.push(item.value)
traversalTree(item.children, that)
}
// 4. 读完了当前级的所有内容并且不是第一级
if (arr.length - 1 === index && that.arrUnlike(arr, that.goodsTypeOptions)) {
valueList.pop()
}
})
}
}
function arrUnlike(arr1, arr2) {
if (arr1.length !== arr2.length) return true
for (const i in arr1) {
if (arr1[i] !== arr2[i]) {
return true
}
}
return false
}
P.s. 递归是世界上最好的语言(逃