递归的理解
递归是一种在函数或算法中调用自身的编程技巧。在递归中,问题被分解为更小的子问题,每个子问题都是原始问题的规模更小的版本,直到达到基本情况(终止条件),然后逐层返回解决方案,最终得到原始问题的解。
递归的两个关键元素
基本情况(终止条件):递归函数必须定义一个或多个基本情况,当满足这些条件时,递归将停止。这是为了避免无限递归,确保递归过程能够终止。
递归调用:递归函数在解决问题时会调用自身,但是每次调用时问题的规模都会减小,逐渐接近基本情况。通过递归调用,问题会被分解为更小的子问题,直到达到基本情况为止。
递归的优势
可以通过简洁的方式解决某些复杂的问题,尤其是涉及到具有递归结构的数据或问题。递归可以使代码更加清晰、易读,并且能够直接基于问题的定义来解决问题。
递归产生的问题
可能导致性能问题和堆栈溢出。递归调用会占用额外的内存空间,而且如果递归深度过大,可能会导致堆栈溢出。因此,在使用递归时,需要确保递归的停止条件能够被满足,并且递归的深度不会过大。
递归内存占用演示
简单写一个递归:不满足if中的条件的时候跳出。
function fn (i) {
if (i < 10) {
i ++;
fn(i);
console.log(i);
}
}
fn(0) // 10,9,8,7,6,5,4,3,2,1
上述代码打印的结果是:10 9 8 7 6 5 4 3 2 1;为什么是倒序输出结果,这里需要结合函数在栈内存(先进后出)中的存储加以演示。
图中每次递归会产生一个新的栈内存,直至达到终止条件执行完整个递归,根据黑色箭头的顺序,从后向前释放内存,最后一个内存先释放,然后依次执行完前面的函数并释放该函数所在内存。
假如递归没有写出口
无限递归:递归函数会无限地调用自身,不会停止。这会导致程序陷入死循环,不断消耗计算资源,最终可能导致栈溢出或程序崩溃。
内存耗尽:每次递归调用都会在内存中创建一个函数执行上下文,如果递归没有终止条件,将会无限地创建新的函数执行上下文,占用大量的内存空间。这可能导致内存耗尽,程序因为无法分配足够的内存而崩溃。
运行时间过长:无限递归会导致程序陷入无限循环,无法得到结果。这会导致程序运行时间非常长,甚至无法正常结束。在实际应用中,这是不可接受的,因为程序需要及时响应和返回结果。
递归必须写出口!!!
递归适用的场景
递归通常适用于解决具有递归结构的问题,即问题可以被自身的子问题所定义或划分。
例如,树的遍历、图的搜索、排列组合等问题常常可以使用递归来实现。
// 递归遍历树
function traverseElTree(node) {
if (!node) {
return;
}
// 处理当前节点
console.log(node.label); // 假设节点有一个 label 属性表示节点的标签文本
// 递归遍历子节点
if (node.children && node.children.length > 0) {
node.children.forEach(child => {
traverseElTree(child);
});
}
}