# Итерация и рекурсия В алгоритмах часто требуется повторное выполнение определенной задачи, что тесно связано с анализом сложности. Поэтому, прежде чем перейти к обсуждению временной и пространственной сложности, рассмотрим, как реализовать повторное выполнение задач в программе, а именно две основные структуры управления программой: итерацию и рекурсию. ## Итерации *Итерация* -- это структура управления, которая позволяет повторно выполнять определенную задачу. В итерации программа повторяет выполнение определенного участка кода, пока выполняется определенное условие. ### Цикл for *Цикл* for -- одна из наиболее распространенных форм итерации, которая подходит для использования, когда количество итераций известно заранее. Следующая функция реализует суммирование 1 + 2 + \... + *n* с использованием цикла for, результат суммирования сохраняется в переменной res. Следует отметить, что в Python диапазон range(a, b) соответствует левому закрытому, правому открытому интервалу, т. е. перебираются значения *a*, *a* + 1, \... , *b* − 1: ```src [file]{iteration}-[class]{}-[func]{for_loop} ``` ![Блок-схема функции суммирования](../assets/iteration_and_recursion.assets/iteration.png) Количество операций этой функции суммирования пропорционально размеру входных данных *n*, или, другими словами, линейно зависит от него. **На самом деле временная сложность описывает именно эту линейную зависимость**. Соответствующий материал будет подробно рассмотрен в следующем разделе. ### Цикл while Подобно циклу for, цикл while также представляет собой метод реализации итерации. В цикле while программа перед каждой итерацией проверяет условие: если условие истинно, то выполнение продолжается, иначе цикл завершается. Ниже приведен пример реализации суммирования 1 + 2 + \... + *n* с использованием цикла while: ```src [file]{iteration}-[class]{}-[func]{while_loop} ``` **Цикл** while **обладает большей степенью свободы по сравнению с циклом** for. В цикле while можно свободно управлять инициализацией и обновлением условной переменной. Например, в следующем коде условная переменная *i* обновляется дважды на каждой итерации, что затруднительно сделать с использованием цикла for: ```src [file]{iteration}-[class]{}-[func]{while_loop_ii} ``` В целом **код с использованием цикла** for **более компактный**, **а цикл** while **более гибкий**. Но они оба могут реализовать итерационную структуру. Выбор между ними определяется требованиями конкретной задачи. ### Вложенные циклы Внутрь одной циклической структуры можно вложить другую, например используя два цикла for: ```src [file]{iteration}-[class]{}-[func]{nested_for_loop} ``` ![Блок-схема вложенного цикла](../assets/iteration_and_recursion.assets/nested_iteration.png) В этом случае количество выполненных действий пропорционально *n*², или, другими словами, время выполнения алгоритма и размер входных данных *n* находятся в квадратичной зависимости. Можно и дальше добавлять вложенные циклы, тогда каждое вложение будет повышать размерность, увеличивая временную сложность до кубической зависимости, зависимости четвертой степени и т. д. ## Рекурсия *Рекурсия* -- это стратегия алгоритма, при которой функция вызывает саму себя для решения задачи. Она включает два основных этапа. 1. **Вызов**: программа постоянно вызывает саму себя, обычно передавая меньшие или более упрощенные параметры, пока не будет достигнуто условие завершения. 2. **Возврат**: после срабатывания условия завершения программа начинает возвращаться из самой глубокой рекурсивной функции, объединяя результаты каждого уровня. С точки зрения реализации рекурсивный код включает три основных элемента. 1. **Условие завершения**: используется для определения момента перехода от вызова к возврату. 2. **Рекурсивный вызов**: соответствует вызову, функция вызывает саму себя, обычно с меньшими или упрощенными параметрами. 3. **Возврат результата**: соответствует возврату, возвращает результат текущего уровня рекурсии на предыдущий уровень. Рассмотрим следующий код: вызов функции recur(n) позволяет вычислить сумму 1 + 2 + \... + *n*. ```src [file]{recursion}-[class]{}-[func]{recur} ``` ![Рекурсивный вызов функции суммирования](../assets/iteration_and_recursion.assets/recursion_sum.png) Хотя с точки зрения вычислений итерация и рекурсия могут давать одинаковый результат, они представляют собой совершенно разные парадигмы мышления и решения задач. - **Итерация**: решение задачи снизу вверх. Начинаем с самых базовых шагов, которые затем повторяются или накапливаются до завершения задачи. - **Рекурсия**: решение задачи сверху вниз. Исходная задача разбивается на более мелкие подзадачи, которые имеют ту же форму, что и исходная задача. Далее подзадачи продолжают делиться на еще более мелкие, пока не достигается базовый случай (решение базового случая известно). Рассмотрим в качестве примера вышеупомянутую функцию суммирования, где решается задача *f*(*n*) = 1 + 2 + \... + *n*. - **Итерация**: моделирование процесса суммирования в цикле проходит от 1 до *n*, выполняя операцию суммирования на каждом шаге, чтобы получить итоговое значение *f*(*n*). - **Рекурсия**: последовательное разбиение задачи на подзадачи вида *f*(*n*) = *n* + *f*(*n* -- 1) до достижения базового случая *f*(1) = 1. ### Стек вызовов Каждый раз, когда рекурсивная функция вызывает саму себя, система выделяет память для нового вызова функции, чтобы хранить локальные переменные, адрес вызова и другую информацию. Это поведение имеет два последствия. - Контекстные данные функции хранятся в области памяти, называемой пространством стекового кадра, и освобождаются только после возврата функции. **Поэтому рекурсия обычно требует больше памяти, чем итерация**. - Рекурсивный вызов функции создает дополнительные накладные расходы. **Поэтому рекурсия обычно менее эффективна по времени, чем циклы**. ![Глубина рекурсивного вызова](../assets/iteration_and_recursion.assets/recursion_sum_depth.png) ### Хвостовая рекурсия ### Дерево рекурсии ## Сравнение итерации и рекурсии