理解递归
一个函数里面调用函数自己本身。
小试牛刀
阶乘
function Factorial(n) {
// 终止条件
if (n == 1) {
return 1;
} else {
// 调用自己
return n * Factorial(n - 1);
}
}
/*
Factorial(2) = 2 * Factorial(1) = 2 * 1;
Factorial(3) = 3 * Factorial(2) = 3 * 2 * Factorial(1) = 3 * 2 * 1
Factorial(4) = 4 * Factorial(3)
*/
let s = Factorial(20);
console.log("s", s); // 2432902008176640000
斐波那契数
// 斐波那契 1 1 2 3 5 8 13 21...
function Fibonacci(n) {
// 终止条件
if (n <= 2) {
return 1;
} else {
// 调用自己
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
}
/*
Fibonacci(1) = 1;
Fibonacci(2) = 1
Fibonacci(3) = Fibonacci(2) + Fibonacci(1) = 1 + 1 = 2
Fibonacci(4) = Fibonacci(3) + Fibonacci(2) = Fibonacci(2) + Fibonacci(1) + Fibonacci(2) = 3
*/
let res = Fibonacci(50);
console.log("res", res); // 12586269025
问题来了
- 问题,当计算 n = 50时,需要花费很长时间来计算,其中Fibonacci(3),Fibonacci(2)等被调用很多次,占用堆栈内存
- 递归非常耗费内存资源,因为需要保存很多个调用帧
- 函数在调用的时,在内存内形成一个调用记录,调用帧,保存调用信息,内部变量等
function foo1() {
console.log("1");
}
function foo2() {
foo1();
}
function foo3() {
foo2();
}
foo3();
/*
foo3在调用时,内部会保存调用帧,foo3被调用时,执行foo2函数...也就是说保存了三个调用帧
*/
尾调用优化
function foo1() {
console.log("1");
}
function foo2() {
return foo1();
}
function foo3() {
return foo2();
}
foo3();
/*
foo3在执行的时候,调用帧只有一个,return foo2 ,foo1就不需要保存foo2 foo1了
*/
尾调用
尾巴上调用,某个函数最后一步是调用另外一个函数
尾调用优化
函数的最后一步操作,是不需要保留其他函数的调用帧, 因为调用的位置,内部变量等不会用到,只需要将最后执行另外的函数之前 return
尾递归
尾调用自身,叫尾递归
优化上面的代码
// 阶乘
function Factorial1(n, total) {
// 终止条件
if (n == 1) {
return total;
} else {
// 调用自己
return Factorial1(n - 1, n * total);
}
}
测试效果
console.time("Factorial");
let s = Factorial(20);
console.log("s", s);
console.timeEnd("Factorial"); // 0.190185546875 ms
console.time("Factorial1");
let s1 = Factorial1(20, 1);
console.log("s1", s1);
console.timeEnd("Factorial1"); // 0.097900390625 ms
// 快了0.1ms 如下图
斐波那契
function Fibonacci2(n, total1 = 1, total2 = 1) {
// 终止条件
if (n <= 2) {
return total2;
} else {
// 调用自己
return Fibonacci2(n - 1, total2, total1 + total2);
}
}
测试效果
console.time("Fibonacci");
let res = Fibonacci(50);
console.log("res", res);
console.timeEnd("Fibonacci"); // 96750.11889648438 ms
console.time("Fibonacci2");
let res3 = Fibonacci2(50);
console.log("res3", res3);
console.timeEnd("Fibonacci2"); // 0.111083984375 ms
// 快了96750 ms 如下图
经典面试题
function getValue(obj, arr) {
if (arr.length == 1) {
return obj[arr[0]];
} else {
return getValue(obj[arr[0]], arr.slice(1));
}
}
let obj = {
a: {
b: {
c: {
d: "100",
},
},
},
};
let arr = ["a", "b", "c", "d"];
let result = getValue(obj, arr);
console.log("result", result); // 100
回溯
回溯四部曲:
- 回溯参数
- 终止条件
- 单层递归逻辑
- 选择其他分支(撤销选择 重置状态)
回溯模版:
result = [];
function backtrack (path, list) {
if (满足条件) {
result.push(path);
return
}
for () {
// 单层逻辑
backtrack (path, list)
// 撤销选择 重置状态
}
}