§ 7. Понятие вспомогательного алгоритма
7.6. Рекурсия
Рекурсия — в программировании вызов подпрограммы (прямо или косвенно) из нее же самой. Количество вложенных вызовов подпрограммы называют глубиной рекурсии. Различают прямую (простую, непосредственную) и косвенную (сложную, опосредованную) рекурсию. Прямая рекурсия: при описании функции используется обращение к ней же самой, но с другим набором параметров. Косвенная рекурсия: подпрограмма вызывает какую-либо другую подпрограмму, в описании которой содержится вызов исходной (например, функция A вызывает функцию B, а функция B вызывает функцию A). Косвенный вызов может быть организован и более сложным образом, т. е. в рекурсию могут быть вовлечены несколько подпрограмм. Рекурсивная программа позволяет описать повторяющиеся действия без явных повторений частей программы и использования циклов. Структура рекурсивной функции содержит команду ветвления. Условие в этой команде определяет, когда рекурсия должна прекратиться. По одной из веток этой команды должен происходить хотя бы один рекурсивный вызов (прямой или косвенный) функцией самой себя. Правильно написанная рекурсивная функция должна гарантировать, что через конечное число рекурсивных вызовов будет достигнуто выполнение условия прекращения рекурсии, в результате чего цепочка последовательных рекурсивных вызовов прервется и выполнится возврат. Любая рекурсивная функция может быть реализована не рекурсивно. Пример 7.14. Описать алгоритм Евклида с помощью рекурсивной функции. Этапы выполнения задания I. Исходные данные: два числа a, b. II. Результат: НОД (a, b). III. Алгоритм решения задачи. 1. Ввод исходных данных. IV. Описание переменных: a, b, c, d — int. Пример 7.15. Написать программу, которая выводит разложение числа на простые множители. Этапы выполнения задания I. Исходные данные: число n. II. Результат: простые множители числа a. III. Алгоритм решения задачи. 1. Ввод исходных данных. 3.1. Параметр а — число которое пытаемся делить, параметр b — число, на которое пытаемся делить. IV. Описание переменных: n — int. С помощью рекурсии удобно реализовывать решение таких задач, в которых сам реализуемый алгоритм по сути рекурсивен. Достаточно просто описать рекурсивную функцию, когда решение задачи может быть описано с помощью рекуррентного соотношения. Пример 7.16. По определению Написать программу, которая находит an, используя описанный выше алгоритм. Этапы выполнения задания I. Исходные данные: числа a и n. II. Результат: значение an. III. Алгоритм решения задачи. 1. Ввод исходных данных. 2.1. Параметр x — основание степени, параметр k — показатель. 3. Вывод результата. IV. Описание переменных: a, n — int. |
В некоторых декларативных или чисто функциональных языках (таких как Пролог или Haskell) нет синтаксических средств для организации циклов. В них рекурсия является единственным доступным механизмом для организации повторяющихся вычислений. Вопрос о желательности использования рекурсивных функций в программировании неоднозначен: с одной стороны, рекурсивная форма может быть структурно проще и нагляднее, в особенности когда сам реализуемый алгоритм по сути рекурсивен. С другой стороны, обычно рекомендуется избегать рекурсивных программ, которые приводят (или в некоторых условиях могут приводить) к слишком большой глубине рекурсии. Классическим примером бесконечной рекурсии являются два поставленные друг напротив друга зеркала: в них образуются два коридора из уменьшающихся отражений зеркал. Рекурсия лежит в основе построения фрактальных изображений. Пример 7.14. V. Программа:
VI. Тестирование: Рекурсивная функция для вычисления НОД двух чисел может быть записана с использованием тернарной (от лат. ternarius — тройной) условной операции.
Операция, структурно записываемая как o1 : o2 : o3, возвращает свой второй (o2) или третий (o3) операнд в зависимости от значения логического выражения, заданного первым операндом (o1). Пример 7.15. V. Программа:
VI. Тестирование: VII. Анализ результата. Чтобы понять, почему мы перебираем простые множители, начиная с меньшего, а программа выводит их в обратном порядке, распишем порядок выполнения команд в программе.
Сохранение вызовов рекурсивной функции вместе со значениями параметров и адресом возврата обеспечивает механизм стека[1] вызовов. Каждый рекурсивный вызов требует некоторого количества оперативной памяти компьютера, поэтому при чрезмерно большой глубине рекурсии может наступить переполнение стека вызовов. Пример 7.16. V. Программа:
VI. Тестирование: |