Files
hello-algo/ru/docs/chapter_computational_complexity/iteration_and_recursion.md
2026-01-20 15:08:42 +08:00

18 KiB
Raw Blame History

Итерация и рекурсия

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

Итерации

Итерация -- это структура управления, которая позволяет повторно выполнять определенную задачу. В итерации программа повторяет выполнение определенного участка кода, пока выполняется определенное условие.

Цикл for

Цикл for -- одна из наиболее распространенных форм итерации, которая подходит для использования, когда количество итераций известно заранее.

Следующая функция реализует суммирование 1 + 2 + ... + n с использованием цикла for, результат суммирования сохраняется в переменной res. Следует отметить, что в Python диапазон range(a, b) соответствует левому закрытому, правому открытому интервалу, т. е. перебираются значения a, a + 1, ... , b 1:

[file]{iteration}-[class]{}-[func]{for_loop}

Блок-схема функции суммирования

Количество операций этой функции суммирования пропорционально размеру входных данных n, или, другими словами, линейно зависит от него. На самом деле временная сложность описывает именно эту линейную зависимость. Соответствующий материал будет подробно рассмотрен в следующем разделе.

Цикл while

Подобно циклу for, цикл while также представляет собой метод реализации итерации. В цикле while программа перед каждой итерацией проверяет условие: если условие истинно, то выполнение продолжается, иначе цикл завершается.

Ниже приведен пример реализации суммирования 1 + 2 + ... + n с использованием цикла while:

[file]{iteration}-[class]{}-[func]{while_loop}

Цикл while обладает большей степенью свободы по сравнению с циклом for. В цикле while можно свободно управлять инициализацией и обновлением условной переменной.

Например, в следующем коде условная переменная i обновляется дважды на каждой итерации, что затруднительно сделать с использованием цикла for:

[file]{iteration}-[class]{}-[func]{while_loop_ii}

В целом код с использованием цикла for более компактный, а цикл while более гибкий. Но они оба могут реализовать итерационную структуру. Выбор между ними определяется требованиями конкретной задачи.

Вложенные циклы

Внутрь одной циклической структуры можно вложить другую, например используя два цикла for:

[file]{iteration}-[class]{}-[func]{nested_for_loop}

Блок-схема вложенного цикла

В этом случае количество выполненных действий пропорционально n², или, другими словами, время выполнения алгоритма и размер входных данных n находятся в квадратичной зависимости.

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

Рекурсия

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

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

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

С точки зрения реализации рекурсивный код включает три основных элемента.

  1. Условие завершения: используется для определения момента перехода от вызова к возврату.

  2. Рекурсивный вызов: соответствует вызову, функция вызывает саму себя, обычно с меньшими или упрощенными параметрами.

  3. Возврат результата: соответствует возврату, возвращает результат текущего уровня рекурсии на предыдущий уровень.

Рассмотрим следующий код: вызов функции recur(n) позволяет вычислить сумму 1 + 2 + ... + n.

[file]{recursion}-[class]{}-[func]{recur}

Рекурсивный вызов функции суммирования

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

  • Итерация: решение задачи снизу вверх. Начинаем с самых базовых шагов, которые затем повторяются или накапливаются до завершения задачи.

  • Рекурсия: решение задачи сверху вниз. Исходная задача разбивается на более мелкие подзадачи, которые имеют ту же форму, что и исходная задача. Далее подзадачи продолжают делиться на еще более мелкие, пока не достигается базовый случай (решение базового случая известно).

Рассмотрим в качестве примера вышеупомянутую функцию суммирования, где решается задача f(n) = 1 + 2 + ... + n.

  • Итерация: моделирование процесса суммирования в цикле проходит от 1 до n, выполняя операцию суммирования на каждом шаге, чтобы получить итоговое значение f(n).

  • Рекурсия: последовательное разбиение задачи на подзадачи вида f(n) = n + f(n -- 1) до достижения базового случая f(1) = 1.

Стек вызовов

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

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

  • Рекурсивный вызов функции создает дополнительные накладные расходы. Поэтому рекурсия обычно менее эффективна по времени, чем циклы.

Глубина рекурсивного вызова

Хвостовая рекурсия

Дерево рекурсии

Сравнение итерации и рекурсии