Кевин Пунгей
Я собираюсь сказать это прямо с летучей мыши. Знаете ли вы события, которые случаются при вызове функций? Нет? Тогда вот где мы начнем.
Вызов функций
Когда мы вызываем функцию, контекст выполнения размещен в стеке выполнения. Давайте сломаем это еще немного.
Во-первых, что такое стек?
Стек представляет собой структуру данных, которая работает на «последнем, первым из первых». Предмет «подтолкнул» на стек, чтобы добавить к нему, и элемент «вышел» от стека, чтобы удалить его.
Использование стека – это способ заказа определенных операций для выполнения.
Теперь, вернемся к тому, что такое контекст исполнения? Контекст выполнения формируется при вызове функций. Этот контекст находится на стеке выполнения, порядка операций. Предмет, который всегда первый в этом стеке, является глобальным контекстом выполнения. Рядом есть какие-либо функции созданные контексты.
Эти контексты выполнения имеют свойства, объект активации и «это» связывание. Связывание «Это» является ссылкой назад к этому контексту выполнения. Объект активации включает в себя: Параметры пройдены, объявленные переменные и объявления функций.
Поэтому каждый раз, когда мы размещаем новый контекст в стеке, у нас обычно есть все, что нам нужно для выполнения кода.
Почему я говорю Обычно ?
С рекурсией мы ждем возвратных значений, исходящих из других контекстов выполнения. Эти другие контексты выше вверх по стеке. Когда последний элемент на стеке завершает выполнение, этот контекст генерирует возвращаемое значение. Это возвращаемое значение передается в виде возвращаемого значения из рекурсивного случая к следующему элементу. То, что контекст выполнения затем выходит из стека.
Рекурсия
Итак, что такое рекурсия?
Рекурсивная функция – это функция, которая вызывает саму собой, пока «базовое условие» верно, а исполнение останавливается.
Пока ложь, мы будем продолжать размещать контексты выполнения поверх стека. Это может произойти, пока у нас не будет «переполнение стека». Переполнение стека – это когда мы исчерпываем память, чтобы удерживать элементы в стеке.
В целом, рекурсивная функция имеет как минимум две части: базовое условие и, по меньшей мере, один рекурсивный случай.
Давайте посмотрим на классический пример.
Факториал
const factorial = function(num) { debugger; if (num === 0 || num === 1) { return 1 } else { return num * factorial(num - 1) }}factorial(5)
Здесь мы пытаемся найти 5! (пять факториалов). факториальная функция определяется как продукт всех положительных целых чисел меньше или равен его аргументу.
Первое условие состояния: «Если параметр пройден равен 0 или 1, мы выйдем и вернемся 1».
Далее рекурсивный случай состояния:
«Если параметр не составляет 0 или 1, то мы будем проходить значение num раз возвращаемое значение вызова этой функции снова с num-1 в качестве его аргумента”.
Так что если мы позвоним факториал (0) , функция возвращает 1 и никогда не попадает в рекурсивный случай.
То же самое относится к факториал (1) Отказ
Мы видим, что происходит, если мы введем оператор отладчика в код и использовать devtools, чтобы нажать его и посмотреть стек вызова.
- Степ исполнения Места
факториал ()с 5 в качестве прошедшего аргумент. Базовый чехол ложный, поэтому введите рекурсивное состояние. - Степ исполнения Места
факториал ()во второй раз сNum-1как аргумент. Базовый чехол является ложным, введите рекурсивное состояние. - Степ исполнения Места
факториал ()В третий раз сNum-1как аргумент. Базовый чехол является ложным, введите рекурсивное состояние. - Степ исполнения Места
факториал ()Четвертый раз сNum-1(3-1) как аргумент. Базовый чехол является ложным, введите рекурсивное состояние. - Степ исполнения Места
факториал ()пятый раз сNum-1как аргумент. Теперь базовый случай верно, поэтому возврат 1.
На данный момент мы уменьшили аргумент на одну на каждую вызов функции, пока не достигнем условия для возврата 1.
6. Отсюда завершится последнее выполнение контекста, оставлять Так что эта функция возвращает 1.
7. Следующий оставлять поэтому возвращаемое значение 2. (1 × 2).
8. Следующий Num , значение возврата SOSCE составляет 6, (2 × 3).
До сих пор у нас есть 1 × 2 × 3.
9. Далее Num , (4 × 6). 24 – это возвращаемое значение для следующего контекста.
10. Наконец, оставлять (5 × 24), и у нас есть 120 в качестве конечного значения.
Рекурсия довольно аккуратная, верно?
Мы могли бы сделать то же самое с циклом для или какого-то времени. Но использование рекурсии дает элегантное решение, которое более читаемо.
Вот почему мы используем рекурсивные решения.
Много раз проблема, разбитая на меньшие части, более эффективна. Разделение проблемы на более мелкие части помощи в завоевании. Следовательно, рекурсия – это подход для деления и завоевания для решения проблем.
- Подзадача легче решить, чем оригинальная проблема
- Решения для подпротесов объединяются для решения оригинальной проблемы
«Divide-and-Conquer» чаще всего используется для прохождения или поиска структур данных, таких как двоичные поисковые деревья, графики и кучи. Это также работает для многих сортировочных алгоритмов, таких как QuickSort. и Heapsort. .
Давайте поработаем через следующие примеры. Используйте devtools, чтобы получить концептуальное понимание того, что происходит, где и когда. Не забудьте использовать операторы отладчика и выйти через каждый процесс.
Фибоначчи.
const fibonacci = function(num) { if (num <= 1) { return num } else { return fibonacci(num - 1) + fibonacci(num - 2) }}fibonacci(5);Рекурсивные массивы
function flatten(arr) { var result = [] arr.forEach(function(element) { if (!Array.isArray(element)) { result.push(element) } else { result = result.concat(flatten(element)) } }) return result}flatten([1, [2], [3, [[4]]]]);
Реверсируя строку
function reverse(str) { if (str.length === 0) return '' return str[str.length - 1] + reverse(str.substr(0, str.length - 1))}reverse('abcdefg');QuickSort.
function quickSort(arr, lo, hi) { if (lo === undefined) lo = 0 if (hi === undefined) hi = arr.length - 1 if (lo < hi) { // partition the array var p = partition(arr, lo, hi) console.log('partition from, ' + lo + ' to ' + hi + '=> partition: ' + p) // sort subarrays quickSort(arr, lo, p - 1) quickSort(arr, p + 1, hi) } // for initial call, return a sorted array if (hi - lo === arr.length - 1) return arr}function partition(arr, lo, hi) { // choose last element as pivot var pivot = arr[hi] // keep track of index to put pivot at var pivotLocation = lo // loop through subarray and if element <= pivot, place element before pivot for (var i = lo; i < hi; i++) { if (arr[i] <= pivot) { swap(arr, pivotLocation, i) pivotLocation++ } } swap(arr, pivotLocation, hi) return pivotLocation}function swap(arr, index1, index2) { if (index1 === index2) return var temp = arr[index1] arr[index1] = arr[index2] arr[index2] = temp console.log('swapped' + arr[index1], arr[index2], +' in ', arr) return arr}quickSort([1, 4, 3, 56, 9, 8, 7, 5])
Практикующие рекурсивные методы важны. Для вложенных данных структур данных, таких как деревья, графики и кучи, рекурсия бесцена.
В будущей статье я обсудим методы оптимизации и памяти хвостовой связи, поскольку они относятся к рекурсии. Спасибо за прочтение!
Дальнейшие ресурсы
Википедия
Программное обеспечение
Другая хорошая статья
M.I.T. OpenCourseware.
Оригинал: “https://www.freecodecamp.org/news/recursion-is-not-hard-858a48830d83/”