‹= РЕКУРСИЯ =›
Рекурсия — это один из способов решения проблем, требующих многократных итераций. При использовании рекурсии по мере выполнения итераций они сохраняются в стеке. После завершения всех итераций стек возвращает значения, которые можно добавить или обработать, создавая правильный возвращаемый результат функции. Создание базового варианта — это первое, что нужно сделать. Поскольку данные будут храниться в стеке, нам нужен выход, базовый вариант позаботится об этом.
Примечания:
* Важно начать процесс, думая о базовом случае, чтобы не переполнить стек и не сломать наш код!
* При рекурсивном мышлении убедитесь, что интерпретатор сохранит частичные результаты в стеке и начнет возвращаться с самого внутреннего вызова рекурсивной функции.
* Существует 2 основных способа создания рекурсивной функции: один — когда рекурсия является основной функцией, а второй — когда у вас есть внешняя функция, а рекурсия происходит как внутренняя функция.
*Полезно начать процесс понимания рекурсии, зная немного о замыкании и о том, как работает стек.
Чтобы упростить понимание и лучше объяснить это, я решил подойти к этому объяснению, используя игрушечную задачу, и работать над ее решением.
Задача называется makeChange, и ее идея состоит в том, что, учитывая множество монет и конечную стоимость, сколькими способами можно расположить любое количество монет, чтобы найти окончательную стоимость.
Вот код:
const makeChange = function(total) { const coins = [1, 2, 5, 10, 20, 50, 100, 200]; let count = 0; const createChange = (target, tracking) => { if (target === total) { count++; } else if (target < total) { for (let i = 0; i < coins.length; i++) { if (i >= tracking) { createChange(coins[i] + target, i); } } } } createChange(0, 0); return count; }; makeChange(200);
Поскольку здесь речь идет о объяснении рекурсии, а не этой проблемы в частности, мы изменим значения массива так, чтобы они заканчивались на индексе 2, а общее значение равнялось 10 вместо 200.
Теперь давайте разберем код и поймем, что делает рекурсивная внутренняя функция…
›› Начнем с создания внешней функции:
const makeChange = function(total) { const coins = [1, 2, 5]; let count = 0; . . . }
Создание внешней функции полезно, потому что она содержит рекурсивную функцию, а код остается чистым и простым в использовании, а также мы можем использовать замыкание для доступа ко всем данным, необходимым между функциями.
›› Время рекурсии
. . . const createChange = (target, tracking) => { if (target === total) { count++; } . . .
Самое первое, что нужно сделать при работе с рекурсией, — это создать базовый случай. Рекурсивные функции повторяются снова и снова, пока не дойдут до базового случая.
Примечание: давайте посмотрим на аргументы нашей рекурсивной функции. У нас есть два аргумента: target и tracking. Идея здесь в том, что target будет накапливать значения монет, а tracking будет отслеживать индекс в массиве монет.
Мы пытаемся подсчитать, сколькими различными способами мы можем добавить монеты, чтобы получить 10. Итак, для нашего базового случая мы хотим добавлять единицу к count каждый раз, когда наша переменная target суммируется. до 10.
. . . else if (target < total) { for (let i = 0; i < coins.length; i++) { if (i >= tracking) { createChange(coins[i] + target, i); } } } } createChange(0, 0); . . .
В этой части кода происходит рекурсия. Мы учитываем случаи, когда наша цель имеет значение ниже, чем наша общая сумма. В данном случае total = 10. Мы также вызываем createChange в первый раз и начинаем его аргументы со значениями 0.
Теперь давайте рассмотрим, как makeChange отслеживает значения в стеке. Происходит первый вызов createChange, и мы начинаем с нашими аргументами значений: target = 0 | tracking = 0.На данный момент наша целевая не равна нашей общей сумме,поэтому мы не достигли базового случая. Итак, мы переходим к нашему оператору else if, который начинает проверку, меньше ли target ( = 0), чем total ( = 10). Войдите в наш цикл for, где i = 0иtracking = 0,мы переходим в наш оператор if, который содержит новый вызов createChange, давайте проверим аргументы, переданные во второй вызов createChange:
coin = [1, 2, 5]; цель = 0; я = 0
цель =› монеты[i] + цель = 1
отслеживание =›я = 0
ВАЖНО: наш первый вызов createChange не завершен, но он сохранен в стеке.
Мы только что сделали новый вызов createChange, что означает сброс нашего цикла, поскольку это новая область действия. В этой итерации есть одно значение, отличное от первого вызова createChange, target = 1.Но этого все еще недостаточно для достижения нашего базового случая, поэтому мы переходим к else if заявление снова. Когда значение target (1) еще меньше total (10), цикл for начинает свою итерацию. Переменные i и tracking по-прежнему равны 0, мы делаем новый вызов createChange, это будет наш третий вызов, поэтому, прежде чем мы начнем Если мы сохраним второй вызов в стеке, в этом случае значение, сохраненное для target, равно 1. Этот процесс будет повторяться, пока мы не достигнем нашего базового случая (target = total).
Вот как выглядит наш стек, когда мы достигаем нашего базового случая:
Scope: Local target: 0 this: Window tracking: 0 Closure (makeChange) Script makeChancge: f (total) values: (3) [1, 2, 5] Global Window Scope: Local target: 1 this: Window tracking: 0 Closure (makeChange) Script makeChancge: f (total) values: (3) [1, 2, 5] Global Window Scope: Local target: 2 this: Window tracking: 0 Closure (makeChange) Script makeChancge: f (total) values: (3) [1, 2, 5] Global Window . . . . Scope: Local target: 9 this: Window tracking: 0 Closure (makeChange) Script makeChancge: f (total) values: (3) [1, 2, 5] Global Window Scope: Local target: 10 this: Window tracking: 0 Closure (makeChange) Script makeChancge: f (total) values: (3) [1, 2, 5] Global Window
Как только target наконец становится равным total, мы достигаем нашего базового случая, и эта итерация рекурсивной функции выполнена. Таким образом, наш стек поднимается на один шаг вверх до случая, когда цель равна 9, и в этот момент цикл for переходит во вторую итерацию, так что теперь i = 1.
Одна из самых важных вещей, которые нужно понять о рекурсии, это то, как стек хранит рекурсивные функции незавершенным образом, пока самый внутренний вызов рекурсивной функции не достигнет своего базового случая.