Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Funktsionalnoe_i_logicheskoe_programmirovanie.doc
Скачиваний:
17
Добавлен:
19.01.2023
Размер:
1.75 Mб
Скачать

5.3 Рекурсия

В предыдущих разделах мы видели, что организовать циклы и ветвления в Прологе можно с использованием механизма поиска с возвратом. Однако, основным инструментом организации циклов в Прологе, так же как и в Haskell, является рекурсия.

Классическим примером, демонстрирующим рекурсию, является вычисление факториала натурального числа. По определению факториала

1!=1 - факториал единицы равен единице,

n!=(n-1)!*n - факториал какого-то числа равен факториалу числа, меньшему на единицу, умноженному на это число.

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

fact(1,1). /* базис рекурсии – факториал единицы равен единице*/

fact(N,F):-M=N-1, /*запишем в новую переменную M значение N-1*/

fact(M,FM), /*вычислим факториал M, результат окажется в переменной FM*/

F=N*FM. /*умножим FM на N*/

Однако при запуске программы для вычисления факториала какого-нибудь натурального числа произойдет ошибка – переполнение стека. Попробуем разобраться, почему так произошло. Например, сделаем запрос fact(3,X). Попытка сопоставления с первым фактом fact(1,1) будет безуспешной. Произойдет вызов второго правила, переменная N станет связанной - N=3. Первая подцель успешно выполнится, M=3-1=2. Далее произойдет вызов предиката fact с первым аргументом, равным 2. Первое сопоставление с фактом неуспешно, переход ко второму правилу, успешное выполнение первой подцели, вызов предиката fact с первым аргументом, равным 1. Успешное сопоставление с фактом, но заносится в стек точка возврата, так как есть еще правило с заголовком fact. Далее происходит успешный возврат из рекурсии и вычисление факториала 3. После чего включается механизм возврата и делается попытка вычислить факториал единицы по второму правилу. При этом M становится равной 0, после чего будет происходить бесконечный вызов предиката с аргументами -1,-2,…. Чтобы избежать этого, требуется убрать ненужную точку возврата при вычислении факториала единицы. Преобразуем первый факт в правило:

fact(1,1):-!.

Теперь предикат будет работать правильно и не скатываться в бесконечную рекурсию. Другим способом решения проблемы является добавление во второе правило подцели, проверяющей , что аргумент N больше 1:

fact(1,1).

fact(N,F):-N>1

M=N-1,

fact(M,FM),

F=N*FM.

Наиболее эффективным способом организации рекурсии является хвостовая рекурсия. Она характеризуется тем, что любой рекурсивный вызов предиката является последней подцелью в правиле, и, кроме того, более ранние подцели не должны содержать иметь точек возврата. В этом случае стек, в котором запоминаются состояния вызывающих предикатов, расходуется более экономно. Очевидно, пример приведенной процедуры вычисления факториала не является хвостовой рекурсией. Однако можно написать новую процедуру с хвостовой рекурсией, если увеличить число аргументов предиката. Пусть первые два аргумента имеют тот же смысл, что и раньше – число, для которого необходимо вычислить факториал, и результат. Добавим к ним еще два аргумента – третий аргумент будет счетчиком вызовов, а четвертый – хранить произведение чисел, начиная с единицы. То есть теперь мы будем сначала вычислять факториал 1, затем умножать результат на 2 и передавать его рекурсивно для вычисления факториала 3 и т.д.

fact1(N,F,C,R):-C<=N, !,/*пока счетчик не более, чем N*/

R1=C*R, /*новое произведение получается умножением текущего счетчика на старое*/

С1=С+1,/*увеличиваем счетчик*/

fact1(N,F,C1,R1). /*рекурсивно вызываем правило с новым счетчиком и новым произведением*/

fact1(_,F,_,F). /*если счетчик равен N, переписываем произведение в результат*/

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

factorial(N,F):-fact1(N,F,1,1).