Front end learning data structure and algorithm quick start series - recursion

Keywords: Javascript Front-end axios Vue.js

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>

If you execute this code in the browser, you will keep asking   Do you understand recursion?, Until you click   confirm   Will end.

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

implement   fibonacci(45)   Since it took 8 seconds, where was the time spent?

If we want to calculate   fibonacci(5) is called as follows:

fibonacci(3)   Called twice, fibonacci(2)   Called 3 times, fibonacci(1)   Called 5 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 recursive version takes 10s, the memory optimized version takes only 0.5ms, which is almost the same as the iterative version.  

Posted by dan231 on Wed, 01 Dec 2021 05:57:34 -0800