Добавил:
natribu.org Все что нашел в интернете скидываю сюда Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Точно Не проект 2 / Не books / Источник_1

.pdf
Скачиваний:
10
Добавлен:
01.02.2024
Размер:
20.67 Mб
Скачать

350

Глава 6

 

 

после(a, b).

после(а, с).

после(b, d). после(b, e).

после(e, g1).

Чтобы выяснить, имеется ли путь из стартовой вершины в целевую вершину, воспользуемся предикатом:

путь(X, X).

путь(X, Y): – после(X, Z),путь(Z, Y).

Иными словами, путь из вершины X в вершину Y существует, если имеется вершина Z, следующая после Х, и известен путь из вершины Z в вершину Y. Факт путь(Х, Х) обеспечивает выход из рекурсии. Для того чтобы выяснить имеется ли путь, ведущий из вершины “ав целевую вершину “g1, необходимо задать вопрос:

? – путь(a, g1).

Данный вариант программы также не возвращает в качестве результата решающий путь, а возвращает ответ “да” или “нет”. Чтобы запомнить решающий путь введем в рассмотрение список закрытых вершин ЗКР, т.е. вершин, которые уже пройдены. Определим предикат, выполняющий поиск в глубину:

в_глубину(Старт, Цель, Реш_путь): – путь(Старт, Цель, [Старт], Путь), реверс(Путь, Реш_путь).

Здесь предикат в_глубину переопределяется через предикат путь. Третьим аргументом предиката путь является список ЗКР. При первом вызове в него помещается стартовая вершина. Четвертый аргумент предиката путь – список вершин, расположенных на обратном пути из целевой вершины в стартовую вершину. Поэтому значение переменной Реш_путь, представляющей решающий путь, получается путем инвертирования списка Путь.

Предикат путь определяется рекурсивно. При этом в списке ЗКР последовательно накапливаются вершины, лежащие на искомом пути:

путь(Х, Х, ЗКР, Путь): – Путь=ЗКР. % выход из рекурсии путь(Х, Y, ЗКР, Путь): – после(X, Z), путь(Z, Y, [Z|ЗКР], Путь).

При каждом рекурсивном вызове очередная вершина Z, следующая после Х, вносится в начало списка ЗКР. Если вершина Z совпадает с целевой вер-

b-d-h,

Язык Пролог

351

 

 

шиной Y, то рекурсивные вызовы прерываются и переменная Путь получает значение списка ЗКР. Если вершина Z является тупиковой, то цель после(Z, _ ) терпит неудачу, и пролог-система выполняет возврат к предыдущей вершине, в которой имеется альтернативный путь для продолжения поиска. В ходе возврата все подстановки, которые были сделаны в список ЗКР на тупиковом направлении, стираются. Чтобы проиллюстрировать это, внесем изменения в определение предиката путь:

путь(X, Y, ЗКР, Путь): – write(ЗКР), nl, после(X, Z), …

Тогда при запросе

? – в_глубину(a, g1, P).

для графа, изображенного на рисунке 6.10, получим следующие ответы:

[a] [b, a]

[d, b, a] [h, d, b, a] [e, b, a] [i, e, b, a]

P=[a, b, e, g1]

В этом случае пролог-система отображает промежуточные состояния списка ЗКР. После захода в вершину hи выяснения того, что цель после(h,_) недостижима, возникает возврат к альтернативному варианту продолжения списка. Подстановки, которые были сделаны в список ЗКР на пути стираются, и список ЗКР получает новое значение [e, b, a].

Рассмотренный вариант программы поиска в глубину имеет существенный недостаток: если на графе состояний имеются циклы, то программа может зациклиться. Чтобы устранить этот недостаток, необходимо добавлять очередную вершину Z в список ЗКР только в том случае, если ее

там нет. Рекурсивное правило для предиката путь перепишется следующим образом:

путь(X, Y, ЗКР, Путь): – после(X, Z), not элемент(Z, ЗКР),

путь(Z, Y, [Z|ЗКР], Путь).

Рассмотрим задачу манипулирования кубиками (рисунок 6.11)1). Имеются три стола p”, “q”, “rи три кубика a”, “b”, “c”. Дано начальное положение кубиков, изображенное на рисунке 6.11 слева. Необходимо найти последовательность команд по перемещению кубиков, обеспечивающую их конечное расположение, изображенное на рисунке 6.11 справа.

1) Данную программу разработал Дж. Криз (J. Kriz)

352

Глава 6

 

 

Рисунок 6.11 – Задача манипулирования кубиками

Данная задача легко решается в пространстве состояний. При этом участок графа состояний, соответствующий переходу из состояния S в состояние S1, будем представлять в виде предиката

t(S, S1, переместить(Что, Откуда, Куда)),

где переместить(Что, Откуда, Куда) – терм, соответствующий команде перемещения кубика. Например, возможный переход из начального состояния, изображенного на рисунке 6.11, можно описать следующим образом:

t(s(p([a, b]), q([ ]), r([c])), s(p[b]), q([a]), r([c])),

переместить(a, откуда(p), куда(q)).

Начальное состояние представлено предикатом s(p([a, b]), q([ ]), r([c])). Аргументы предиката – термы, отражающие расположение кубиков на столах. Так, p([a, b]) означает, что на столе pнаходятся кубики aи b, причем кубик aнаходится сверху кубика b. Новое состояние S1, в которое необходимо перейти, описывается предикатом s(p([b]), q([a]), r([c])). Переход в это состояние обеспечивается командой переместить(a, откуда(p), куда(q)), то есть необходимо переставить кубик aсо стола pна стол q.

Введем следующие правила, определяющие возможные перемещения кубиков:

t(s(p[P1|Ps], q(Q), r(R)), s(p(Ps), q(Q1), r(R1)),

переместить(P1, откуда(р), куда(D))): – (D=q, Q1=[P1|Q], R1=R);

(D=r, Q1=Q, R1=[P1|R]). t(s(P, q([Q1|Qs]), r(R)),

s(P, q(Qs), r(R1)),

переместить(Q1, откуда(q), куда(r))): – R1=[Q1|R].

Первое правило позволяет перемещать кубик (D=q), либо на стол r” (D=r). При этом кубик

Р1 со стола pна стол qР1 устанавливается сверху

Язык Пролог

353

 

 

кубиков, которые находились либо на столе q” (Q1=[P1|Q]), либо на столе r” (R1=[P1|R]). Второе правило обеспечивает перемещение кубиков со стола qна стол r.

Решение задачи можно найти с помощью следующего предиката поиска в глубину:

в_глубину(Старт, Решение): – путь(Старт, [Старт], Решение).

путь(S, ЗКР, [ ]): – цель(S). путь(S, ЗКР, [T|Ts]): –

t(S, S1, T), not элемент(S1, ЗКР), путь(S1, [S1|ЗКР], Ts).

Здесь в переменной Решение будет накапливаться последовательность команд по перемещению кубиков. Список ЗКР, как и прежде, служит для исключения повторного прохождения через одни и те же состояния. Предикат цель(S) определяет целевое состояние. Для рисунка 6.11 целевое состояние задается фактом:

цель(s(p([ ]), q([ ]), r([a, b, c]))).

Зададим вопрос

? – в_глубину(s(p([a, b]), q([ ]), r([c])), Решение).

Тогда пролог-система найдет следующее решение

Решение=[переместить(а, откуда(p), куда(q)), переместить(b, откуда(р), куда(r)), переместить(а, откуда(q), куда(r))].

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

путь(S, ЗКР, [T|Ts], Глубина): – Глубина>0, Глубина1 is Глубина-1, t(S, S1, T),

путь(S1, [S1|ЗКР], Ts, Глубина1).

При очередном выборе ветви поиска, значение переменной Глубина уменьшается на единицу. В этом случае поиск идет сначала в глубину, а затем в ширину.

в_ширину( ):

354

Глава 6

 

 

6.11.2.2. Поиск в ширину. Программирование метода поиска в ширину сложнее, чем поиска в глубину. Обусловлено это тем, что необходимо запоминать несколько возможных направлений поиска. Напишем программу поиска в ширину в соответствии с алгоритмом, рассмотренным в главе 2.

Введем в рассмотрение предикат в_ширину(ОТК,ЗКР,Решение), где ОТК – список открытых вершин; ЗКР – список закрытых вершин; Решение

список обследованных вершин, соответствующий списку ЗКР в момент достижения целевой вершины. Указанные списки состоят из подсписков, каждый из которых содержит два элемента. Первый элемент подсписка – это имя некоторой вершины графа состояний, а второй – имя ее родительной вершины. Список Решение формируется таким образом, что его первый подсписок в качестве первого элемента содержит целевую вершину. Просмотрев список Решение от целевой вершины до стартовой вершины, можно восстановить решающий путь. Будем использовать для этого пре-

дикат решающий_путь(Решение, Цель, Старт, Temp, Реш_путь), где Цель

целевая вершина; Старт – начальная вершина; Temp – вспомогательная переменная, предназначенная для накопления результатов поиска; Реш_путь

список, фиксирующий найденный путь от стартовой до целевой вершины.

При определении предиката в_ширину(ОТК,ЗКР,Решение) для раскрытия очередной вершины и генерации возможных продолжений пути будем использовать встроенный предикат bagof. Цель

bagof(X, C, L)

формирует список L всех объектов Х, удовлетворяющих условию С. Например,

после(a, b). после(a, c). после(b, d). после(d, h). после(a, e). после(e, k).

? – bagof([Z | a ], после(a, Z),L). L = [[b | a], [c | a], [e | a]]

В этом случае в списке L накапливаются подсписки, содержащие дочерние вершины для вершины ‘a’.

Используя рекурсию, определим предикат

в_ширину([[О1|О2]|Ост_ОТК ], ЗКР,Решение):– цель(О1),!, Решение = [[О1|О2]|ЗКР].

Язык Пролог

355

 

 

в_ширину([[О1|О2]|Ост_ОТК], ЗКР, Решение):– bagof([Z|О1], (после(O1,Z), not элемент1(Z, ЗКР),

not элемент1(Z,Ост_ОТК), L),

слияние(Ост_ОТК, L, Нов_ОТК), !, в_ширину(Нов_ОТК, [[О1|О2]|ЗКР], Решение); в_ширину(Ост_ОТК, ЗКР, Решение).

В начале поиска список ЗКР пустой, а список ОТК содержит подсписок, включающий начальную вершину. На каждом шаге рекурсии выделяется первый подсписок [О1|О2] списка ОТК. Выясняется, является ли вершина О1 целевой (цель(О1)). Если О1 целевая вершина, то подсписок [О1|О2] добавляется в начало списка ЗКР, и рекурсия прерывается. Переменная Решение получает значение списка [[О1|О2] | ЗКР], элементами которого являются подсписки, представляющие пройденные отрезки графа состояний.

Если О1 не целевая вершина, то находятся все продолжения пути после вершины О1. Для этого применяется предикат bagof, который обеспечивает поиск всех отрезков графа состояний [Z|O1] таких, что вершина Z не входит в список ЗКР и в остаток списка ОТК (Ост_ОТК). Все найденные дочерние вершины накапливаются в списке L в виде подсписков и помещаются в конец списка Ост_ОТК, образуя список Нов_ОТК. Затем обработанный первый подсписок [О1|О2] списка ОТК перемещается в начало списка ЗКР, и предикат в_ширину вызывается при новых значениях аргументов.

Если вершина О1 является тупиковой, то предикат после(О1,Z) завершается неудачей. В этом случае никакие новые вершины не добавляются в конец списка ОТК, и обеспечивается альтернативный вызов предиката

в_ширину:

в_ширину(Ост_ОТК, ЗКР, Решение).

В рассмотренном определении предиката в_ширину используется предикат элемент1(Вершина,Список). Данный предикат позволяет установить, входит ли Вершина в один из подсписков, образующих Cписок:

элемент1(H, [[H|T] | _] ). элемент1(T, [[H|T] | _] ).

элемент1(H, [[Y|Z] |Ост] ):– элемент1(Н,Ост).

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

решающий_путь( _, Х, Х, Temp,Реш_путь) :–Реш_путь=Temp. решающий_путь(Решение, Цель, Старт, Temp, Реш_путь):– найти(Цель, Решение, [[H1|T1] | Ост1] ),

слияние(Temp, [[H1|T1]], Temp1), найти(T1, Ост1, [[H2|T2] | Ост2]),

356

Глава 6

 

 

слияние(Temp1, [[H2 | T2]], Temp2),

решающий_путь(Ост2, Т2, Старт, Temp2, Реш_путь).

Предикат найти(Элемент,Список1,Список2) выполняет поиск заданного элемента в списке Список1, состоящем из подсписков. Результатом поиска является список Список2, представляющий хвостовую часть списка Список1, в первый подсписок которого входит искомый элемент:

найти(Н, [[H|T]|Ост], [[H|T]|Ост] ). найти(Н, [[Y|T]|Ост], X):–

найти(Н, Ост, Х).

Чтобы лучше уяснить работу описанных выше предикатов, введем вопрос:

? – в_ширину( [[a | a]], [ ], Решение),

решающий_путь(Решение, g1, a, [ ], Реш_путь).

Начальное значение списка ОТК = [[a | a]]. Список ЗКР пустой. Цель заключается в том, чтобы найти решающий путь из начальной вершины "a" в конечную вершину g1(рисунок 6.10). В результате обработки вопроса получим значения переменных:

Решение = [[g1 | e], [e | b], [d | b], [c | a], [b | a], [a | a]] Реш_путь = [[g1 | e], [e | b], [b | a], [a | a]]

Значение переменной Решение соответствует состоянию списка ЗКР в момент завершения поиска. Головным элементом каждого из подсписков является вершина, которая раскрывалась в процессе поиска (за исключением целевой вершины). Хвостовой элемент каждого из подсписков есть родительная вершина для головного элемента. Перемещаясь по указанным подспискам в направлении от целевой вершины до начальной, используя ссылки на родительские вершины, восстанавливаем значения переменной

Реш_путь.

6.11.2.3. Эвристический поиск. В эвристических алгоритмах список открытых вершин ОТК сортируется с учетом возрастания оценочной функции. Рассмотрим А– алгоритм с оценочной функцией вида (2.1).

Пусть граф состояний описывается с помощью фактов, задаваемых предикатом после(P,D,C,H), где Р – родительская вершина; D – дочерняя вершина; С – стоимость участка пути между вершинами Р и D; Н – оценка стоимости кратчайшего пути из вершины D в целевую вершину. Тогда граф состояний, изображенный на рисунке 2.9, можно описать с помощью следующего набора фактов:

после(s, d, 1, 10). после(s, c, 3, 7).

Язык Пролог

357

 

 

после(s, b, 5, 4). после(s, a, 7, 1). после(a, g, 10, 0). после(b, a, 1, 1). после(c, b, 1, 4,). после(c, a, 2, 1). после(d, c, 1, 7).

Определим на Прологе предикат а_поиск, соответствующий процедуре эвристического поиска A_search (см. § 2.2.2.3):

a_поиск(ОТК, ЗКР, Решение),

где ОТК, ЗКР – списки открытых и закрытых вершин; Решение список, соответствующий искомому пути из начальной вершины в целевую вершину. Каждая из вершин графа состояний представляется в этих списках в виде подсписков [D, P, F, G], где D – вершина на графе состояний; P – ссылка на родительную вершину; F – значение оценочной функции (2.1); G

– оценка стоимости пути от начальной вершины до вершины D. Определение предиката а_поиск близко к определению предиката

в_ширину. Отличие заключается в том, что в случае эвристического поиска вершины из списка ЗКР могут перемещаться обратно в список ОТК. Происходит это из-за того, что в ходе поиска может быть обнаружен путь меньшей стоимости к вершине, которая уже была раскрыта. Это приводит к необходимости удаления такой вершины из списка ЗКР и вставки ее в список ОТК (с учетом значения оценочной функции). Кроме этого, если в процессе раскрытия очередной вершины обнаруживается, что ее дочерние вершины уже имеются в списке ОТК, но с бóльшими значениями оценочной функции, то такие вершины также удаляются из списка ОТК, и вместо них в список ОТК вставляются вершины с меньшими значениями оценочной функции. Приведем определение предиката а_поиск:

а_поиск([D, P, F, G] | Ост_ОТК], ЗКР, Решение):–

цель(D), !, Решение = [[D,P,F,G] | ЗКР].

а_поиск([[D,P,F,G] | Ост_ОТК], ЗКР, Решение):– bagof([Z,D,FZ,GZ], следующая(D,Z,FZ,GZ,G]), DB),! ,

вставить([D,P,F,G], ЗКР, Н_ЗКР), поместить(Ост_ОТК,DB,Н_ЗКР,Нов_ОТК, Нов_ЗКР), а_поиск(Нов_ОТК, Нов_ЗКР, Решение); а_поиск(Ост_ОТК, ЗКР, Решение).

В этом определении с помощью предиката bagof находятся все дочерние вершины для вершины D, находящейся на первом месте в списке

358

Глава 6

 

 

ОТК и имеющей наименьшее значение оценочной функции F. Для поиска дочерних вершин используется условие:

следующая(D,Z,FZ,GZ,G): –

после(D,Z,C,H), GZ is G+C, FZ is GZ+H.

Здесь переменные FZ и GZ соответствуют оценочным функциям fˆ(n) и gˆ(n) для вершины Z. Переменная G соответствует оценке gˆ(n) для вершины D. При вызове предиката переменные D и G конкретизированы, а переменные Z, FZ, GZ вычисляются, формируя подсписки [Z,D,FZ,GZ], которые накапливаются в списке дочерних вершин DB. Если вершина D может быть раскрыта (т. е. имеет дочерние вершины), то вызов bagof завершается успешно, и вершина D перемещается в список ЗКР. Это выполняется с помощью вызова:

вставить( [D,P,F,G], ЗКР, Н_ЗКР).

Дочерние вершины для вершины D, размещаемые в списке DB, вносятся в остаток списка ОТК (Ост_ОТК), если они отсутствуют в списках Ост_ОТК и Н_ЗКР. Если дочерняя вершина уже имеется в списке Ост_ОТК, а значение её оценочной функции выше, чем у вершины из списка DB, то она заменяется на вершину из списка DB. Кроме того, если дочерняя вершина имеется в списке Н_ЗКР, то в список Ост_ОТК вносится соответствующая вершина из списка DB. Все указанные действия выполняются с помощью вызова предиката

поместить(Ост_ОТК, DB, Н_ЗКР,Нов_ОТК, Нов_ЗКР).

В результате выполнения предиката формируются новые списки Нов_ОТК и Нов_ЗКР. После этого предикат а_поиск вызывается рекурсивно для новых значений списков ОТК и ЗКР. Если у вершины D не имеется дочерних вершин, то вызов bagof завершается неудачей, и предикат а_поиск вызывается рекурсивно при старых значениях списков ОТК и ЗКР. Рекурсия прерывается, когда первая вершина в списке ОТК будет целевой. При этом она вносится в начало списка ЗКР, формируя значение перемен-

ной Решение.

Предикат поместить определяется следующим образом:

поместить(Ост_ОТК, [ ], ЗКР, Ост_ОТК, ЗКР). поместить(Ост_ОТК, [X | Ост_DB], ЗКР, Нов_ОТК, Нов_ЗКР): –

(not элемент(Х, Ост_ОТК), not элемент(Х, ЗКР), !, вставить(Х, Ост_ОТК, Н_ОТК), Н_ЗКР = ЗКР;

удалить(Х,Ост_ОТК,Нов),вставить(Х,Нов,Н_ОТК), Н_ЗКР = ЗКР; удалить(Х,ЗКР,Н_ЗКР), вставить(Х, Ост_ОТК, Н_ОТК)), поместить(Н_ОТК,Ост_DB, Н_ЗКР, Нов_ОТК, Нов_ЗКР).

Нов_Список
вставить(Х,Список,Нов_Список)

Язык Пролог

359

 

 

Предикат удалить(Х,Список,Нов_список) выполняет удаление подсписка Х, представляющего вершину графа состояний, из списка Список. Результатом является новый список Нов_Список. Удаление выполняется, если оценочная функция для удаляемого элемента Х меньше значения оценочной функции элемента из списка Список.

удалить([ZD,PD,FZD,GZD], [[Z, _, FZ, _] | T], T):– ZD==Z, FZD= FZ,!. удалить(Х, [Y|T], [Y|T1]):– удалить(Х,Т,Т1).

Предикат осуществляет включение подсписка Х, представляющего вершину графа состояний, в Список, формируя новый список Нов_Список. При этом включение выполняется с учетом значения оценочной функции. Результирующий список упорядочен по возрастанию оценочной функции:

вставить(X,[ ],Нов_Список):– Нов_Список = [X].

вставить([ZD,PD,FZD,GZD], [ [Z,P,FZ,GZ] | Ост], Нов_Список): – FZD FZ, !,

Нов_Список = [[ZD,PD,FZD,GZD], [Z,P,FZ,GZ] | Ост]. вставить(Х, [H|T], [H|T1]):– вставить(Х,Т,Т1).

Предикат элемент(Х,Список) в рассматриваемом случае определяется следующим образом:

элемент([Z | X], [[Z | _ ] | T] ).

элемент(Н, [Y | T] ):– элемент(Н, Т).

Рассмотрим выполнение эвристического поиска на примере графа состояний, изображенного на рисунке 2.9. При этом будем выводить на печать состояния списков ОТК и ЗКР при каждом вызове предиката а_поиск. Введем целевое утверждение

? – а_поиск( [[s, s, 14, 0]], [ ], Решение).

Здесь список ОТК содержит начальную вершину, представленную подсписком [s, s, 14, 0]. Список ЗКР пустой. В ходе поиска будут получены следующие значения списков ОТК и ЗКР:

OTK=[[a,s,8,7],[b,s,9,5],[c,s,10,3],[d,s,11,1]], ЗКР=[[s,s,14,0]], OTK=[[b,s,9,5],[c,s,10,3],[d,s,11,1],[g1,a,17,17]], ЗКР=[[a,s,8,7],[s,s,14,0]], OTK=[[a,b,7,6],[c,s,10,3],[d,s,11,1],[g1,a,17,17]], ЗКР=[[b,s,9,5],[s,s,14,0]], OTK=[[c,s,10,3],[d,s,11,1],[g1,a,16,16]], ЗКР=[[a,b,7,6],[b,s,9,5],[s,s,14,0]], OTK=[[a,c,6,5],[b,c,8,4],[d,s,11,1],[g1,a,16,16]],

Соседние файлы в папке Не books