写点东西《JavaScript 中的递归》
- 您是否曾经发现自己需要在 JavaScript 中循环遍历一个复杂的多维对象,却不知道如何操作?
- 那么,递归函数到底是什么?
- 让我们回到我们的树对象。
- 为什么使用递归
- 🌟更多精彩
您是否曾经发现自己需要在 JavaScript 中循环遍历一个复杂的多维对象,却不知道如何操作?
如果是这样,您应该考虑使用搜索引擎作为查找解决方案的强大工具。但是,既然您已经在这里,如果您继续阅读,您就会找到解决此问题的优雅解决方案。
让我们以以下树为例开始。
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
函数解决了我们的初始问题。
它首先打印当前节点的名称(作为参数传递)。然后,它循环遍历当前节点的子节点数组,并针对每个子节点调用自身(再次递归),将子节点作为新参数传递。此过程一直持续到它打印树中的所有节点名称。这正是我们所说的我们需要的东西。
此函数演示了遍历树结构的常见模式:处理当前节点(在本例中,打印其名称),然后递归处理所有子节点。
这种技术被称为深度优先遍历,因为它在回溯之前尽可能深入树中。
为什么使用递归
如果您出于某种原因尚未被说服使用递归函数,我将为您提供四个递归函数如此有用的原因。
- 简单性:递归通常比其迭代对应项(for、while 等)更容易理解。它们可以将复杂的任务变成更简单的任务。
- 问题解决:有些问题本质上是递归的,例如树遍历、河内塔等。对于此类问题,使用递归函数更容易。
- 分而治之:递归函数允许您将较大的问题分解为较小、更易于管理的子问题。这是许多高效算法(如归并排序和快速排序)的基础。
- 更少的代码:递归函数可以减少代码量。好的,更短的代码并不一定意味着更好,但它可以使代码更具可读性。
总之,递归不仅是某些问题的优雅解决方案,而且是将任务分解为更小任务(例如遍历树或对列表进行排序)的强大且有用的工具。
它们的关键组件是基本情况(函数停止调用自身的情况)和递归情况(函数调用自身的部分)。
但是,与迭代解决方案相比,它们可能更难理解和调试,并且如果未仔细实现,还可能导致堆栈溢出等性能问题。因此,谨慎使用递归并确保始终可以访问基本情况非常重要。
🌟更多精彩
点击👉这里~~