Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Вариант_№28.doc
Скачиваний:
2
Добавлен:
01.07.2025
Размер:
801.79 Кб
Скачать

Рекурсия

Рекурсивным называется объект, частично состоящий или определяемый с помощью самого себя. В математике рекурсия применяется повсеместно, например, следующее определение рекурсивно: "0 является натуральным числом; натуральное число плюс единица есть натуральное число".

С точки зрения реализации различают прямую рекурсию (когда процедура содержит ссылку на себя) и косвенную (когда процедура содержит ссылку на другую процедуру, содержащую ссылку на исходную). Более подробная информация о рекурсии вообще может быть найдена в источнике (Вирт, 1986).

В большинстве процедурных языков программирования рекурсия является скорее нежелательным методом программирования (вследствие возможности переполнения стека), однако в Lisp она является не только допустимой без ограничений, но, более того, является основным инструментом организации циклов. В чистом Lisp операторы DO и LOPP, а тем более PROG отсутствуют, и должны быть заменены рекурсией. Здесь мы рассмотрим основные приёмы использования этого механизма.

Простейшая рекурсия заключается в том, что некоторая функция вызывает сама себя для проведения некоторых вычислений. Рекурсия отражает рекуррентные соотношения (когда в ряду значений каждое новое вычисляется на основании предыдущих). Простейшим примером является факториал, который задаётся в частности как n!=n* (n-1)!. В примере с функцией DO было рассмотрено вычисление факториала с помощью цикла. С помощью рекурсии можно вычислить его так:

>(defun fc2 (x)

(cond

((eql x 0) 1)

((eql x 1) 1)

(T (* x (fc2 (- x 1))))))

FC2

>(fc2 5)

120

Как видно из примера, рекурсивный вызов полностью отражает рекуррентное соотношение, описывающее факториал. Тем не менее, можно с помощью такого же метода организовывать и циклы, например, в следующем примере функция создаёт список из элементов от 1 до n

>(defun lst2 (x lst)

(cond

((eql x 0) lst)

(T (lst2 (- x 1) (cons x lst)))))

LST2

>(LST2 5 nil)

(1 2 3 4 5)

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

>(defun err1 () (err1))

ERR1

>(err1)

Error: Stack overflow (signal 1000)

[condition type: SYNCHRONOUS-OPERATING-SYSTEM-SIGNAL]

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

  • В теле функции всегда должно присутствовать правило, гарантирующее выход из рекурсии.

  • Правила, гарантирующие выход из рекурсии, должны предшествовать правилам, содержащим рекурсию

Даже при соблюдении таких правил зацикливание всё равно возможно, поэтому необходимо использовать отладку. Отладка рекурсивных функций является, вообще говоря, не слишком простым делом. Для неё используется функция TRACE

trace

(trace функция)

Функция позволяет выводить на экран дерево вызовов некоторой функции.

untrace

(untrace функция)

Функция позволяет отменить режим вывода дерева вызовов некоторой функции, установленный функцией trace.

В качестве примера приведём всё то же вычисление факториала:

>(defun fc2 (x)

(cond

((eql x 0) 1)

((eql x 1) 1)

(T (* x (fc2 (- x 1))))

))

FC2

>(fc2 5)

120

>(trace fc2)

(FC2)

>(fc2 5)

0: (FC2 5)

1: (FC2 4)

2: (FC2 3)

3: (FC2 2)

4: (FC2 1)

4: returned 1

3: returned 2

2: returned 6

1: returned 24

0: returned 120

120

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

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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]