个人主页:《爱蹦跶的大A阿》
当前正在更新专栏:《VUE》 、《JavaScript保姆级教程》、《krpano》
递归作为一种能够用简洁的方式定义复杂对象的编程技巧,在计算机科学中被广泛应用。它借助系统堆栈的先入后出结构,将大问题拆分为小问题来解决,对于二叉树、组合问题等都是非常高效的解决方案。
但是递归也有其局限性,它占用堆栈空间,存在最大调用层数限制。为了发挥递归技巧的优势,同时规避其缺点,我们需要深入理解递归的原理及其与堆栈的关系,掌握改写递归的技巧。
本文将从递归和堆栈的基本概念出发,剖析两者之间的内在联系,分析递归技巧的优缺点,给出其典型应用场景,并通过示例讲解递归代码的实现思路。最后,将提供改写递归的实用技巧,帮助读者更好地运用这一编程工具。
递归是一种编程技巧,它会直接或间接地调用函数自身来解决问题,就像一面镜子可以不断产生镜中的镜像一样。递归会把大问题分解为小问题来解决,小问题的解决方案又依赖于大问题的解决。
堆栈是计算机管理函数调用的一种数据结构,它采用先进后出的方式存储信息。当一个函数开始执行时,会被添加到堆栈的顶部;当一个函数执行结束,就会被堆栈删除。递归函数的实现就是利用了这种堆栈结构。
一个递归函数必须同时满足以下两个条件:
只要同时满足这两个条件,就可以使用递归来解决问题。
递归函数在执行时会保存每一层的函数调用现场信息,这些信息被保存在系统的运行时堆栈中。可以把递归看成是不断把函数调用现场信息压入堆栈,并在满足基准条件时逐层将堆栈弹出并恢复函数的执行现场。
优点:
缺点:
(1)计算阶乘
function factorial(n) {
if (n == 1) return 1;
return n * factorial(n - 1);
}
(2)斐波那契数列
function fib(n) {
if (n <= 2) return 1;
return fib(n-1) + fib(n-2);
}
以二叉树前序遍历为例:
function preorder(root) {
if (!root) return;
console.log(root.val);
preorder(root.left);
preorder(root.right);
}
比如实现DFS深度优先遍历:
function dfs(graph, current, visited) {
visited.add(current);
for (let neighbor of graph[current]) {
if (!visited.has(neighbor)) {
dfs(graph, neighbor, visited);
}
}
}
例如归并排序:
function mergeSort(arr) {
if (arr.length === 1) {
return arr;
}
const mid = Math.floor(arr.length / 2);
const left = arr.slice(0, mid);
const right = arr.slice(mid);
return merge(mergeSort(left), mergeSort(right));
}
以计算阶乘为例,递归代码如下:
function factorial(n) {
if (n === 1) {
return 1
}
return n * factorial(n - 1)
}
factorial(5) // 120
满足基准条件(n === 1)时返回1,否则不断调用自身计算n*(n-1)的阶乘。
理解递归技巧的本质在于掌握递归与堆栈的关系。递归允许程序以简洁的方式定义复杂对象,是计算机科学中一把双刃剑。运用得当,可以事半功倍;使用不当,后果不堪设想。
本文全面解析了递归及其与堆栈的千丝万缕的联系,剖析了递归技巧的优劣势,并提供了改写递归的有效方法。希望这些知识可以帮助读者在运用递归技巧时既发挥它的高效优势,又规避其潜在缺陷。