如果是这样,您应该考虑使用搜索引擎作为查找解决方案的强大工具。但是,既然您已经在这里,如果您继续阅读,您就会找到解决此问题的优雅解决方案。
让我们以以下树为例开始。
const mySuperComplexTree = {
name: "root",
children: [
{
name: "child-1",
children: [
{
name: "child-1-1",
children: [
{
name: "child-1-1-1",
children: []
}
]
}
]
},
{
name: "child-2",
children: []
}
]
};
问题:您必须将每个节点名称(包括根节点)打印到控制台。
当我们第一次遇到这类问题时,我们倾向于被邓宁-克鲁格效应所误导,并认为可以用一个简单的 for 循环来解决。简短的回答是肯定的……但同样,那并不总是最好的方法。
让我们看看当我们遵循这种方法时会发生什么:
console.log(mySuperComplexTree.name);
for (const node of mySuperComplexTree.children) {
console.log(node.name);
for (const node2 of node.children) {
console.log(node2.name);
for (const node3 of node2.children) {
console.log(node3.name);
}
}
}
// Output:
// root
// child-1
// child-1-1
// child-1-1-1
// child-2
GIF
正如您可能看到的,即使它起作用了,此解决方案也存在几个问题:
**
这就是递归派上用场的地方。**
GIF
它是一个在执行期间调用自身的函数。此特性允许它用于解决可以分解为与整体问题相同的更小、更简单的子问题的问题。
一个简单的方法是使用递归来实现倒计时功能。让我们看看。
function countDownFrom(n) {
if (n < 0) return;
console.log(n);
countDownFrom(n - 1);
}
countDownFrom(10);
在此示例中, countDownFrom
函数打印一个数字,然后使用您传递的数字(减一)调用自身(递归),重复此过程,直到达到基本情况(在本例中,当 n 小于 0 时)。
正如你所见,它基本上是一个循环,但更简单、更优雅。
我们的初始问题是,我们需要打印树的所有节点名称,我们将使用递归来解决它,因为我们注意到我们的树具有一个常量结构,其中每个节点都有一个 name
属性(我们要打印的字符串)和一个 children
属性(一个节点数组)。
因此,我们的函数需要接收一个节点并打印其名称,然后由于所有子节点都是相似的,我们可以循环一次子节点并仅调用传递子节点的相同函数。这将接收一个母鹿,打印其名称并重复该过程。
function printNodeNames(tree) {
console.log(tree.name);
for (const node of tree.children) {
printNodeNames(node);
}
}
printNodeNames(mySuperComplexTree);
// Output:
// root
// child-1
// child-1-1
// child-1-1-1
// child-2
正如您所见, printNodeNames
函数解决了我们的初始问题。
它首先打印当前节点的名称(作为参数传递)。然后,它循环遍历当前节点的子节点数组,并针对每个子节点调用自身(再次递归),将子节点作为新参数传递。此过程一直持续到它打印树中的所有节点名称。这正是我们所说的我们需要的东西。
此函数演示了遍历树结构的常见模式:处理当前节点(在本例中,打印其名称),然后递归处理所有子节点。
这种技术被称为深度优先遍历,因为它在回溯之前尽可能深入树中。
如果您出于某种原因尚未被说服使用递归函数,我将为您提供四个递归函数如此有用的原因。
总之,递归不仅是某些问题的优雅解决方案,而且是将任务分解为更小任务(例如遍历树或对列表进行排序)的强大且有用的工具。
它们的关键组件是基本情况(函数停止调用自身的情况)和递归情况(函数调用自身的部分)。
但是,与迭代解决方案相比,它们可能更难理解和调试,并且如果未仔细实现,还可能导致堆栈溢出等性能问题。因此,谨慎使用递归并确保始终可以访问基本情况非常重要。
点击这里~~