Мне трудно понять порядок вызовов в рекурсивном программировании в Javascript

Играя с рекурсивным программированием в Javascript, я хотел найти решение для случая использования Фибоначчи.

Учитывая номер индекса N, вернуть соответствующее значение последовательности Фибоначчи, где последовательность: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ...).

Что я сделал со следующим решением (я знаю, что смогу улучшить его с помощью памятки, чтобы избежать экспоненциальной сложности):

function fibonacci(n) {
  if (n <= 1) return 1;
  return fibonacci(n-1) + fibonacci(n-2);
}


Поскольку я хотел понять это лучше, я начал добавлять console.log() в мой код. И вот тогда произошло сногсшибательное 🤯.

Порядок звонков, счетчик и шаги не имеют никакого смысла для меня!

function fibonacci(n, caller = 'initalFibonacciCaller', step = 0) {
  console.log(caller)
  console.log('n =', n)
  console.log('step =', step)
  console.log('----------------')

  if (n <= 1) return 1;
  step++
  return fibonacci(n-1, 'fibonacciCaller1', step) + fibonacci(n-2, 'fibonacciCaller2', step);
}

console.log('=>', fibonacci(4))

Отклик:

initalFibonacciCaller
n = 4
step = 0
----------------
fibonacciCaller1
n = 3
step = 1
----------------
fibonacciCaller1
n = 2
step = 2
----------------
fibonacciCaller1
n = 1
step = 3
----------------
fibonacciCaller2
n = 0
step = 3
----------------
fibonacciCaller2
n = 1
step = 2
----------------
fibonacciCaller2
n = 2
step = 1
----------------
fibonacciCaller1
n = 1
step = 2
----------------
fibonacciCaller2
n = 0
step = 2
----------------

5

Почему с step3 fibonacciCaller2 n начинают увеличиваться, а steps начинают уменьшаться?
Вероятно, это связано с моим непониманием того, как работает Javascript, но мне бы хотелось получить хорошее объяснение по этому поводу.

1
Thomas Sohet 1 Май 2020 в 12:06

2 ответа

Последовательность чисел Фибоначчи определяется по формуле Fn = Fn-1 + Fn-2. То есть следующее число получается как сумма двух предыдущих.

Первые два числа: 1, затем 2 (1 + 1), затем 3 (1 + 2), 5 (2 + 3) и т. Д .: 1, 1, 2, 3, 5, 8, 13, 21 .. .,

Числа Фибоначчи рекурсивны по определению:

function fib(n) {
  return n <= 1 ? n : fib(n - 1) + fib(n - 2);
}

При больших значениях n такое решение будет работать очень долго. Например, fib (77) может на некоторое время повесить браузер, пожирая все ресурсы процессора.

Это потому, что функция порождает обширное дерево вложенных вызовов. В этом случае ряд значений вычисляется много раз снова и снова.

...
fib(5) = fib(4) + fib(3)
fib(4) = fib(3) + fib(2)
...

Здесь видно, что значение fib (3) необходимо одновременно для fib (5) и fib (4). Он будет рассчитан два раза, полностью независимо.

Вы можете заметить, что fib (3) вычисляется дважды, а fib (2) - три раза. Общее количество вычислений растет намного быстрее, чем n, что делает его огромным даже при n = 77.

Дерево полной рекурсии: введите описание изображения здесь

0
Denys Levshenkov 1 Май 2020 в 09:21

Вы можете использовать другой подход и визуализировать сторону рекурсии, добавив угловые скобки как шаг и получить порядок вызова со стороной.

Например, рекурион сначала идет к левой стороне бифуркации, а затем на последнем шаге к правой стороне.

function fibonacci(n, step = { s: 0 }, sides = '') {
    console.log(++step.s, n, sides);
    if (n <= 1) return 1;
    return fibonacci(n - 1, step, sides + '< ') + fibonacci(n - 2, step, sides + '> ');
}

console.log(fibonacci(5));
.as-console-wrapper { max-height: 100% !important; top: 0; }
0
Nina Scholz 1 Май 2020 в 09:22