Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
AlgStr / Библиотека / ЛЕКЦИИ / POSIBNIK / ПРОГР НА ПРОЛОГЕ.doc
Скачиваний:
42
Добавлен:
23.03.2015
Размер:
669.7 Кб
Скачать

5.4. Повторення та рекурсія (пр)

Правило, що містить само себе як компонент, називається правилом рекурсії. Такі правила також можуть застосовуватися для реалізації повторного виконання деякої роботи в програмі на ПРОЛОЗі.

Наприклад:

друк_вітання:-

write(“Привіт!!!”), nl,

друк_вітання.

Процес буде виконуватися нескінченно. У реальних умовах нескінченна рекурсія, як правило, завершується через переповнення стека. Розмір стека в системі TP можна збільшити за допомогою опції Options\Compile directives\Memory allocations\Stack size. Але, якщо рекурсивне правило не генерує покажчики відкоту і наступна підціль правила - рекурсивний виклик самого правила, то TP усуне додаткові витрати, викликані рекурсією. Цей процес називається усуненням хвостової рекурсії. Тому наша програма дійсно спричинить зациклення.

Спробуємо уникнути виникнення нескінченної рекурсії. Напишемо процедуру друку “привітання” певну кількість раз. Відповідно до загальних принципів організації кінцевих рекурсивних обчислень наша процедура повинна мати принаймні два твердження: для тривіального та рекурсивного випадку. Одержимо:

друк_декількох_вітань(0).

write_count(Count):-

Count1 = Count –1,

write(“Hello”), nl,

друк_декількох_вітань(Count1).

Щоб викликати друк на екрані рядка “Привіт” рівно 7 разів необхідно задати ціль “друк_декількох_вітань(7)”.

Розглянемо іншу задачу: необхідно ввести символи з клавіатури й виконати їх еховивід на екран. Роботу завершити при введенні деякого спеціального символу (наприклад, “#”).

друк_запрошення:-

write(“Уводите довільні символи“), nl, nl,

write(“ Для завершення – уведіть символ #”), nl, nl.

еховивід_уведених_символів:-

readchar(Ch),

Ch <> “#”,

write(Ch),

еховивід_уведених_символів.

еховивід_уведених_символів.

Розв’язання задачі ініціюється викликом цілі:

Ціль: друк_запрошення, еховивід_уведених_символів.

Така структура предиката - типова для організації рекурсії. Розглянемо більш докладно структуру побудови таких предикатів.

5.5. Метод узагальненого правила рекурсії (упр)

Щоб рекурсивне правило могло коли-небудь закінчитись, воно повинно включати умову виходу. Тому структура узагальненого рекурсивного правила повинна бути такою:

< ім'я правила рекурсії > :-

< список предикатів >,

< предикат умови виходу >,

< список предикатів >,

< ім'я правила рекурсії >,

< список предикатів >.

Предикат умови виходу перевіряє, чи може інтерпретатор ПРОЛОГу дістатись до правила рекурсії для виконання нового рекурсивного виклику. Тобто це правило розрізняє тривіальний випадок від нетривіального. Це правило може обрамлятися якимись іншими предикатами, які що-небудь готують для такої перевірки або для рекурсивного виклику. Іноді таку перевірку можна виконати в заголовку правила, тому три перших групи предикатів у структурі конкретного рекурсивного правила можуть бути відсутні.

Особливо слід розглянути останній список предикатів, що викликаються після правила рекурсії. До нього можна дістатись тільки в тому разі, якщо правило рекурсії завершиться тривіальним випадком (без рекурсивного виклику) і крім того – завершиться успішно. Рекурсивна процедура, що складається з єдиного правила, структура якого наведена вище, ніколи не завершиться успішно, тому що тривіальний випадок – це невдача предиката перевірки умови виходу. Тому це правило необхідно поширити, як мінімум, ще одним твердженням для тривіального випадку. Як правило, таке твердження ставиться першим у процедурі. Одержимо остаточний вигляд рекурсивної процедури, побудованої за методом УПР:

< ім'я правила рекурсії > :-

< список правил для розв’язання тривіального випадку >.

< ім'я правила рекурсії > :-

< список предикатів >,

< предикат умови виходу >,

< список предикатів >,

< ім'я правила рекурсії >,

< список предикатів >.

Дотепер нічого не говорилося про параметри рекурсивного предиката. А їх наявність обов'язкова. Інакше не буде можливості відрізнити тривіальний випадок від рекурсивного. Як відомо, копія параметрів і локальних змінних (а в ПРОЛОЗі всі змінні локальні) для кожного рекурсивного виклику зберігається на стеці. Тому список предикатів у першому твердженні, що викликається після правила рекурсії, може їх одержати тільки зі стека!

Розглянемо конкретні приклади побудови таких рекурсивних процедур.

Приклад 1. Надрукувати на екрані послідовність натуральних чисел.

друк_натуральних(8).

друк_натуральних(Number):-

Number < 8,

write(Number), nl,

Next_number = Number + 1,

друк_натуральних(Next_number).

Звернутися до даного предиката можна за допомогою цілі “write_number(1)”. Слід відзначити, що перевірка Number < 8 буде потрібна тільки тоді, коли до даної прецедури ми повернемося при відкоті. Отже, перше твердження завжди спрацює і не пропустить до другого твердження процедури, якщо натуральні числа будуть перебиратися послідовно (з кроком 1). Але ми не знаємо, в якому контексті буде застосовуватися наша процедура, тому дана перевірка бажана. Вона робить нашу процедуру детермінованою. Детермінована процедура поводиться однаково як при першому виклику, так і при відкоті. Цього ж ефекту можна домогтися й за допомогою відсікання:

друк_натуральних(8) :- !.

друк_натуральних(Number):-

write(Number), nl,

Next_number = Number + 1,

друк_натуральних(Next_number).

Приклад 2. Обчислити суму ряду.

сума_ряду(1, 1).

сума_ряду(Number, Sum):-

Number > 0,

Next_number = Number – 1,

сума_ряду(Next_number, Portial_sum),

Sum = Number + Portial_sum.

Звернутися до предиката можна за допомогою цілі “сума_ряду(7, Сума)”.

Приклад 3. Обчислити факторіал натурального числа.

факт(0, 1) :- !.

факт(Number, Result):-

Next_number = Number –1,

факт(Next_number, Portial_fact),

Result = Number * Portial_fact.

Тут ми використовували відсікання для перетворення предиката на детермінований. Звернутися можна за допомогою цілі “факт(7, Факт)”.