- •Глава 1. Введение в пролог
- •1. Декларативные и процедурные языки программирования
- •2. Пролог и логика предикатов. Внешние цели
- •3. Управление программой. Подцели. Механизм сопоставления
- •4. Внутренние подпрограммы унификации
- •Глава 2. Внутренние цели. Механизм возврата
- •1. Структура пролог-программы
- •2. Использование внутренних целей
- •3. Встроенный предикат fail
- •4. Сокращенные варианты внутренних запросов
- •5. Использование в запросах анонимных переменных
- •6. Механизм возврата
- •Глава 3. Типы данных и арифметика Turbo Prolog
- •1. Стандартные типы данных
- •2. Структуры, простые и составные
- •3. Структурные диаграммы
- •4. Использование в запросах анонимных переменных
- •5. Использование альтернативных доменов
- •6. Арифметика в Turbo Prolog
- •Глава 4. Предикат отсечения (!). Программирование альтернатив. Правила повтора
- •1. Повторения и возвраты
- •2. Отсечение (!)
- •3. Программирование альтернатив
- •4. Правило повтора
- •Глава 5. Методы организации рекурсии
- •1. Простая рекурсия
- •2. Метод обобщенного правила рекурсии
- •3. Граничное условие рекурсии. Нисходящая и восходящая рекурсии
- •4. Программа о подсчете числа точек
- •Глава 6. Списки
- •1. Основные понятия
- •2. Списки и турбо-пролог
- •3. Атрибуты списка
- •4. Внутреннее представление списков
- •5. Применение списков в программе
- •6. Метод разделения списка на голову и хвост
- •7. Поиск элемента в списке
- •8. Присоединение списка
- •9. Добавление и удаление элемента
- •10. Подсписок
- •11. Перестановки списка
- •Глава 7. Сортировка списков
- •1. Разделение списка на два
- •2. Сортировка списков методом вставки
- •3. Быстрая сортировка
- •4. Быстрая сортировка_1
- •5. Компоновка данных в список
- •Глава 8. Программирование алгоритмов с возвратом. Представление графов в turbo prolog
- •1. Задача о весах
- •2. Представление графов в turbo prolog
- •3. Поиск пути на неориентированном графе
- •4. Поиск гамильтоновых циклов
- •5. Поиск пути минимальной стоимости
- •Глава 9. Динамическая база данных
- •1. Турбо-пролог и реляционные базы данных
- •2. Описание предикатов динамических бд
- •3. Встроенные предикаты asserta, assertz, retract, retractall, save, consult
- •4. Создание динамической базы данных
- •5. Обсуждение проекта базы данных
- •6. Создание базы данных
- •7. Написание программных модулей
- •Глава 10. Глобальные переменные в turbo prolog
- •1. Модификация базы данных
- •2. Накопление результатов с помощью вынуждаемого возврата
- •3. Подсчет членов парторганизации
- •4. Поиск пути минимальной стоимости от a до z
- •Библиографический список
- •Оглавление
Глава 5. Методы организации рекурсии
1. Простая рекурсия
Правило, содержащее само себя в качестве компоненты, называется ПРАВИЛОМ РЕКУРСИИ. Правила рекурсии так же, как правила повтора, реализуют повторное выполнение задач. Они весьма эффективны, например, при формировании запросов к базе данных, а также при обработке таких доменных структур, как списки. Этот метод подходит для целого ряда применений, начиная с обработки файлов и кончая математическими вычислениями.
Если рекурсивное правило не генерирует указателей возврата, и последняя подцель правила является рекурсивным вызовом самого правила, то Турбо-Пролог устранит дополнительные расходы, вызываемые рекурсией.
Этот процесс называется УСТРАНЕНИЕМ ХВОСТОВОЙ РЕКУРСИИ.
Пример простого правила рекурсии:
write_string :- /* выдать строку */
write("У ПОПА БЫЛА СОБАКА..."),
nl,
write_string.
Это правило будет бесконечно вызывать само себя.
Избежать возникновения бесконечной рекурсии можно. Для этого следует ввести предикат завершения, содержащий условие выхода.
Формулировка условия выхода на русском языке для правила write_string может иметь вид: «Продолжать печать строки до тех пор, пока счетчик печати не превысит число 7. После чего остановить процесс».
Правило read_a_char демонстрирует простое правило рекурсии, в которое включено условие выхода. Программа циклически считывает символ, введенный пользователем: если этот символ не #, то он выдается на экран, если этот символ — #, то программа завершается.
Простое правило рекурсии с условием выхода:
read_a_char :-
readchar(Ch),
Ch <> '#',
write(Ch),
read_a_char.
2. Метод обобщенного правила рекурсии
Обобщенное правило рекурсии также содержит в теле правила само себя. Рекурсия будет конечной, если в правило включено условие выхода, гарантирующее окончание его работы. Ответственность за обеспечение завершаемости правила рекурсии лежит на программисте.
Тело правила состоит из утверждений и правил, определяющих задачи, которые должны быть выполнены. Ниже в символическом виде дан общий вид правила рекурсии:
<имя правила рекурсии> :-
(1) <список предикатов>,
(2) <предикат условия выхода>,
(3) <список предикатов>,
(4) <имя правила рекурсии>,
(5) <список предикатов>.
Данное правило рекурсии имеет пять компонент. Первая — это группа предикатов. Успех или неудача любого из них на рекурсиию не влияет.
Следующая компонента — предикат условия выхода. Успех или неудача этого предиката либо позволяет продолжить рекурсию, либо вызывает ее остановку. Третья компонента — список других предикатов. Аналогично, успех или неудача этих предикатов на рекурсию не оказывает влияния.
Четвертая группа — само рекурсивное правило. Успех этого правила вызывает рекурсию.
Пятая группа — список предикатов, успех или неудача которых не влияет на рекурсию. Пятая группа может использовать значения, помещенные в стек во время выполнения рекурсии.
Правила, построенные указанным образом, являются обобщенными правилами рекурсии (ОПР), а метод называется ОПР-методом.
Например, вы хотите написать правило генерации всех целых чисел, начиная с 1 и кончая 7. Пусть имя правила будет write_number(Number).
write_number(8).
write_number(Number) :-
Number < 8,
write(Number), nl,
Next_Number = Number + 1,
write_number(Next_number).
Для этого примера первая компонента структуры общего правила рекурсии не используется. Второй компонентой, т.е. предикатом выхода, является Number < 8. Когда значение Number равно 8, правило будет успешным и программа завершится.
Третья компонента правила оперирует с числами. В этой части правила число выдается на экран и затем увеличивается на 1.
Для увеличенного числа будет использоваться новая переменная Next_Number. Четветая компонента — вызов самого правила рекурсии write_number(Next_number). Пятая компонента, представленная в общем случае, здесь не используется.
Программа начинается с попытки вычислить подцель write_number(1). Заметим, что необязательно вызывать правило, используя то же имя переменной, что используется в голове правила. Это всего лишь позиция в списке параметров, имеющая значение при передаче значений. Фактически, если не передавать значение Next_Number, то приращение основного числа программы невозможно. При рекурсивном вызове головы правила, программа снова пытается выполнить подцель write_number(8). Программа продолжает выполнять цикл сопоставления, присвоения и выдачи значений Number до тех пор, пока значение Number не станет равным 8. В этот момент цель выполнена, правило успешно и программа завершается.
Упражнение 5.1.
Измените правило рекурсии write_string так, чтобы результатом программы была семикратная печать строки
Используем правило рекурсии для вычисления суммы ряда целых чисел от 1 до 7:
S(7) = 1 + 2 + 3 + 4 + 5 + 6 + 7 = 28
Правило рекурсии программы выполняет вычисления по обычной схеме сложения:
частичная сумма + следующее значение.
Правило 1 рекурсии имеет вид:
sum_series(1,1). /* сумма1 ряда */
sum_series(Number,Sum) :-
Number > 1,
Next_number = Number — 1,
sum_series(Next_number,Partial_Sum),
Sum = Number + Partial_Sum.
Данное правило имеет четыре компоненты и одно дополнительное нерекурсивное правило. Заметим, что последняя компонента правила рекурсии — это правило Sum с Partial_Sum (частичная сумма) в качестве переменной. Это правило не может быть выполнено до тех пор, пока Partial_Sum не получит некоторого значения.
Программа Сумма1 начинается с попытки выполнить подцель sum_series(7,Sum). Сначала программа пытается сопоставить подцель с подправилом sum_series(1,1). Сопоставление неудачно. Затем она пытается сопоставить подцель с sum_series(Number,Sum). На этот раз сопоставление завершается успешно с присвоением переменной Number значения 7. Затем программа сравнивает значение Number, которое равно 7, с 1 т.е. проверяется условие выхода. Так как 7 больше 1, то сопоставление успешно, программа переходит к следующему подправилу. Для этого подправила переменной Next_number присвоено значение 6, т.е. значение Number — 1. Затем правило вызывает само себя в виде sum_series(6,Partial_Sum). Следующим подправилом является правило Sum, содержащее свободную переменную Partial_Sum. Оно не может быть вызвано до тех пор, пока Partial_Sum не получит значения.
Теперь программа пытается сопоставить факт sum_series(1,1) с sum_series(6,Partial_Sum). Процесс сопоставления неуспешен, поскольку несопоставим ни один из параметров.
В результате программа переходит к следующему правилу с головой sum_series(Number,Sum), присваивая переменной Number значение 6. Этот циклический процесс сопоставления продолжается до тех пор, пока не будет получено sum_series(1,Partial_Sum). Теперь это правило сопоставляется с sum_series(1,1), а Partial_Sum приписывается значение 1. Переменная Sum получает, наконец, значение 3 (1+2).
Во время процесса сопоставления переменная Partial_Sum была свободна, а программа запоминала значения Number для последующего использования. Теперь эти значения извлекаются из стека, а Sum получает последовательно значения 6,10,15,21 и 28. Конечное значение Sum есть 28.
При возвратах, когда цель sum_series(1,Partial_Sum) сопоставится с правилом sum_series(Number,Sum) условие Number > 1 не даст
Next_number получить значение —1, а программе войти в бесконечную рекурсию.
/* Программа 5.1 «Сумма ряда». Назначение:*/
/*демонстрация использования рекурсивного */
/*предиката подсчета суммы S(N) ряда S, */
/* где N положительное целое число */
domains
number, sum = integer
predicates
sum_series(number, sum)
goal
sum_series(7,Sum),
write("Сумма ряда:"),nl,nl,
write(" S(7) = ", Sum), nl.
clauses
sum_series(1,1). /* сумма1 ряда */
sum_series(Number,Sum) :-
Number > 0,
Next_number = Number — 1,
sum_series(Next_number,Partial_Sum),
Sum = Number + Partial_Sum.
/* Конец программы */
Заменим правило sum_series на правило sum_series2. Правило sum_series2 является модификацией правила sum_series. Модификация выполнена посредством удаления условия выхода Number > 1 и введения правила
sum_series2(1,1) :- !.
вместо факта
sum_series(1,1).
Правило sum_series2 рекурсии:
sum_series2(1,1) :- !. /* сумма2 ряда */
sum_series2(Number,Sum) :-
Next_number = Number — 1,
sum_series2(Next_number,Partial_Sum),
Sum = Number + Partial_sum.
Результаты работы этих правил идентичны. Их следует рассматривать как альтернативные варианты. Сразу возникает вопрос: где во втором правиле условие выхода?