recursion
The concept of recursion
Recursion is a method to solve a problem. It starts from solving each small part of the problem to solving the initial big problem.
Recursion usually involves calling the function itself, directly calling itself, or indirectly calling itself, which are recursive functions. Like this:
// Call itself directly function fn1(){ fn1() }
// Call itself indirectly function fn2(){ fn3() } function fn3(){ fn2() }
Now fn1() will be executed all the time, so each recursive function must have a condition that is not called recursively (i.e. baseline condition) to prevent infinite recursion.
There is a famous saying: to understand recursion, we must first understand recursion. We translate it into javascript code:
<script> function Understanding recursion() { const answer = confirm('Do you understand recursion?') if (answer) { return } Understanding recursion() } Understanding recursion() </script>
When you execute this code in the browser, you will keep asking if you understand recursion?, It will not terminate until you click OK.
Calculate the factorial of a number
A positive integer Factorial (factorial) is the product of all positive integers less than or equal to the number, and the factorial of 0 is 1. Factorial writing of natural number n n!
That is n= one × two × three ×...× (n-1) × n
The factorial of 5 is expressed as 5!, Equal to 1 * 2 * 3 * 4 * 5, i.e. 120
See recursive implementation:
// The default n is a positive integer greater than or equal to 0 function factorial(n) { // Baseline conditions if (n <= 1) { return 1 } // Recursive call return n * factorial(n - 1) } console.log(factorial(5)) // 120
Maximum call stack size exceeded
What happens if you forget to add a stop condition to a recursive function? Like this:
<script> let i = 0 function fn4() { i++ return fn4() } try { fn4() } catch (e) { console.log(`i : ${i} error : ${e}`) } </script>
Test:
// Google Chrome v95 i : 13955 error : RangeError: Maximum call stack size exceeded // Microsoft Edge v95 i : 13948 error : RangeError: Maximum call stack size exceeded
In chrome v95, the function executes 13955 times and finally throws an error: RangeError: the maximum call stack size is exceeded. Therefore, it is very important to have a baseline condition to stop recursion.
Tip: es6 has tail call optimization, which means that this code will be executed all the time. see Compatibility table You will find that most browsers do not support proper tail calls (tail call optimization), so they are not expanded.
Fibonacci number
Fibonacci sequence (Fibonacci sequence) refers to such a sequence: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
The number 2 is obtained by 1 + 1, the number 3 is obtained by 2 + 1, the number 5 is obtained by 3 + 2, and so on.
Fiboracci sequence is defined as follows:
- The fiboracci number at position 0 is 0
- The fiboracci number of positions 1 and 2 is 1
- The Fibonacci number at position n (where n > 2) is the Fibonacci number of (n - 1) plus the Fibonacci number of (n - 2).
See recursive implementation:
function fibonacci(val) { if (val <= 1) { return val } return fibonacci(val - 1) + fibonacci(val - 2) } // 0 1 1 2 3 5 for (let i = 0; i <= 5; i++) { console.log(fibonacci(i)) }
Is recursion faster
We use console.time() To detect two versions of fibonacci function (iterative implementation vs recursive implementation):
// Iterative calculation of fiboracci number function fibonacciIterative(n) { let pre = 1 let prePre = 0 let result = n for (let i = 2; i <= n; i++) { result = pre + prePre; [prePre, pre] = [pre, pre + prePre] } return result }
Test:
console.time('fibonacciIterative()') console.log(fibonacciIterative(45)) console.timeEnd('fibonacciIterative()') console.time('fibonacci()') console.log(fibonacci(45)) console.timeEnd('fibonacci()') // 1134903170 // fibonacciIterative(): 0.579ms // 1134903170 // fibonacci(): 8.260s
Tests show that the iterative version is much faster than the recursive version.
However, the iterative version is easier to understand and requires less code. In addition, for some algorithms, the iterative solution may not be available.
Memory optimization technology
Since it took 8 seconds to execute fibonacci(45), where was the time spent?
If we want to calculate fibonacci(5), the call is as follows:
fibonacci(3) was called twice, fibonacci(2) was called three times, and fibonacci(1) was called five times.
We can save the result. When we need to calculate it again, we don't need to repeat the calculation and return the result directly. Rewrite fibonacci() as follows:
const fibonacciMemoization = (function () { const mem = [0, 1] function fibonacci(val) { // In the cache, it returns directly if (mem[val]) { return mem[val] } if (val <= 1) { return val } const result = fibonacci(val - 1) + fibonacci(val - 2) // Store in cache mem.push(result) return result } return fibonacci }())
Test:
let num = 45 console.time('fibonacci()') console.log(fibonacci(num)) console.timeEnd('fibonacci()') console.time('fibonacciIterative()') console.log(fibonacciIterative(num)) console.timeEnd('fibonacciIterative()') console.time('fibonacciMemoization()') console.log(fibonacciMemoization(num)) console.timeEnd('fibonacciMemoization()') // 1134903170 // fibonacci(): 10.590s // 1134903170 // fibonacciIterative(): 0.513ms // 1134903170 // fibonacciMemoization(): 0.506ms
Although the iterative version takes 10s, the time spent memorizing the optimized version and the iterative version is almost the same (0.5ms).