递归算法时数据结构中的重要思想,但对于算法问题来说,利用递归思想解决问题有几种模式可以总结下来
简单直接的调用自己即为简单递归,典型题目:
- 求解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 并行数限制