递归算法时数据结构中的重要思想,但对于算法问题来说,利用递归思想解决问题有几种模式可以总结下来
简单直接的调用自己即为简单递归,典型题目:
- 求解n!
function fn(n) {
if (n === 0) return 1;
return n * fn(n - 1);
}
函数最后一步是递归,并且最后的递归操作只返回自己,经典题目
- 求解n!的尾递归写法
function fn(n, accumulator) {
if (n === 0) return accumulator;
return fn(n - 1, accumulator * n);
}
注:普通递归,会在每次递归时创建新的栈帧,尾递归则会复用栈帧,不新建栈帧,所以相对而言会将空间复杂度将为O(1)
- 为什么要创建新的栈帧?
每次调用都需要一个独立的执行上下文(包括参数、局部变量、返回地址等)。创建新的栈帧使得函数调用可以独立、安全地执行,不会干扰其他调用或被其他调用干扰
循环中嵌套递归,是一种常用的讲解算法问题的方式。通过循环中嵌套递归,可以进行树和图的遍历,包括DFS、BFS等;可以达到分治的目的,分解大问题成小问题;还可以达到回溯的目的,看是否满足要求;还有对一些嵌套数据的处理等
- 从嵌套数据中查找
//从address中查找匹配id的数据
const address = {
id: "1",
name: "北京市",
children: [
{
id: "1-1",
name: "东城区",
},
{
id: "1-2",
name: "西城区",
},
{
id: "1-3",
name: "海淀区",
children: [
{
id: "1-3-1",
name: "北京大学",
},
{
id: "1-3-2",
name: "中关村",
children: [
{
id: "1-3-2-1",
name: "海淀黄庄",
},
],
},
],
},
{
id: "1-4",
name: "朝阳区",
children: [
{
id: "1-4-1",
name: "奥林匹克公园",
},
],
},
],
};
//非递归写法
function findAddressById(address, id) {
if (address.id === id) {
return address;
}
let queue = address.children ? address.children : [];
while (queue.length > 0) {
let ch = queue.shift();
if (ch.id === id) {
return ch;
}
if (ch.children) {
queue = queue.concat(ch.children);
}
}
return null;
}
//递归写法
function findAddressById(address, id) {
if (address.id === id) {
return address;
}
for (let i = 0; i < address?.children?.length; i++) {
if (address.children[i].id === id) {
return address.children[i];
}
let re = findAddressById(address.children[i], id);
if (re) {
return re;
}
}
return null;
}
//let r = findAddressById(address, "1-3-1");
//console.log(r);
// {
// id: '1-3-1',
// name: '北京大学',
// }
- promise 并行数限制