Рекурсия
Рассмотрим другое определение факториала. В нем используются счетчик C и накопитель для хранения произведения первых C натуральных чисел:
class predicates fact: (positive, unsigned64 [out]). fact1: (positive, positive, unsigned64, unsigned64 [out]). clauses fact(N, F):- fact1(N, 0, 1, F). fact1(N, N, F, F):- !. fact1(N, C, X, F):- fact1(N, C + 1, (C + 1) * X, F).
Снова проследим вычисления для цели fact(3, R). Эта цель унифицируется с заголовком правила:
fact(N1, F1):- fact1(N1, 0, 1, F1).
При этом N1 = 3, F1 = R. Далее вызывается цель
fact1(3, 0, 1, R).
Эта цель унифицируется только с заголовком последнего правила:
fact1(N2, C2, X2, F2):- fact1(N2, C2 + 1, (C2 + 1) * X2, F2).
При этом N2 = 3, C2 = 0, X2 = 1, F2 = R. Теперь вызывается цель
fact1(3, 1, 1, R).
Она также унифицируется только с заголовком последнего правила:
fact1(N3, C3, X3, F3):- fact1(N3, C3 + 1, (C3 + 1) * X3, F3).
При этом N3 = 3, C3 = 1, X3 = 1, F3 = R. Вызывается цель:
fact1(3, 2, 2, R).
Она унифицируется с тем же правилом:
fact1(N4, C4, X4, F4):- fact1(N4, C4 + 1, (C4 + 1) * X4, F4).
Теперь N4 = 3, C4 = 2, X4 = 2, F4 = R. Новая цель имеет вид:
fact1(3, 3, 6, R).
Эта цель унифицируется с заголовком правила:
fact1(N5, N5, F5, F5):- !.
При этом N5 = 3, F5 = 6, R = 6. Отсечение предотвращает поиск других решений. Итак, имеем: R = 6.
Это был пример восходящей рекурсии. В восходящей рекурсии промежуточные результаты вычисляются на каждом шаге рекурсии, так что ответ строится постепенно и передается в виде параметра рабочей памяти до тех пор, пока не будет достигнута конечная ситуация. К этому моменту ответ уже будет вычислен.
В языке Visual Prolog имеется оптимизация хвостовой рекурсии. Копии вызываемых предикатов не создаются, а вычисления ведутся в области памяти предиката-родителя. Это позволяет многократно увеличить эффективность вычислений.
Рассмотрим недетерминированный рекурсивный предикат. В следующей программе рекурсивно определяется отношение "предок" как транзитивное замыкание отношения "родитель" (рис. 5.1).
class facts - relatives parent: (string Родитель, string Ребенок). clauses parent("Иван", "Мария"). parent("Анна", "Мария"). parent("Мария", "Павел"). parent("Мария", "Петр"). parent("Петр", "Степан"). class predicates ancestor: (string Предок, string Потомок) nondeterm (o,o) (i,o). clauses ancestor(X, Y):- parent(X, Y). ancestor(X, Y):- parent(X, Z), ancestor(Z, Y). run():- ancestor(X, Y), write(X, " - ", Y), nl, fail; _ = readLine().Пример 5.1. "Предок"
Упражнение 1.
-
Постройте дерево поиска для цели
ancestor("Мария", D).
- Определите отношение "потомок" как транзитивное замыкание отношения "родитель".