Изменить стиль страницы

переход(Х, Y,T,):- Д(Х,Z),not(принадлежит(Z,Т)), переход(Z,Y,[Z|T]).

Словами это может быть выражено так: для того чтобы «перейти» из X в Y, не проходя через комнаты из списка Т, надо найти дверь из X куда-либо (т. е. в Z), убедиться, что Z еще не занесена в список Т, и «перейти» из Z в Y, используя список Т с дописанной в него Z.

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

Наша программа рассматривает каждую дверь как одностороннюю. Если мы считаем, что наличие двери из комнаты а в комнату b – это то же самое, что наличие двери из комнаты b в комнату а, то, как отмечалось выше, мы должны указать это явно. Кроме повторного задания д-фактов с перестановкой аргументов, имеются два способа задать эту информацию в самой программе. Самый очевидный способ – это добавить еще одно правило, получая в итоге:

переход(Х,X,T).

переход(X,Y,T):- д(X,Z), not(принадлежит(Z,Т)),переход(Z,Y[Z|T]). переход(Х,Y,T):- д(Z,Х), not(принадлежит(Z,Т)),пeреход(Z,Y,[Z|T]).

Или, используя предикат ';' (обозначающий дизъюнкцию), можно записать:

переход(Х,Х,Т).

переход(Х,Y,T):- (д(Х,Z); д(Z,Х)), not(принадлежит (Z,T)),пepexод(Z,Y,[Z|T]).

Теперь о том, как найти телефон. Рассмотрим целевое утверждение есть_телефон(X), которое согласуется с базой данных, если в комнате X есть телефон. Если мы хотим сказать, что в комнате g есть телефон, то мы просто записываем в нашу базу данных факт есть_телефон(g). Предположим, мы начали поиск с комнаты а. Один из способов узнать дорогу к телефону – это задать вопрос:

?- переход(а,Х,[]), есть_телефон(X).

Это – вопрос типа «создать и проверить», который находит достижимые комнаты и затем проверяет наличие в них телефона. Другой способ – это найти сопоставление сначала для предиката есть_телефон(Х), а затем попробовать перейти из комнаты а в X:

?- есть_телефон(Х), переход(а,Х,[]).

Последний метод более эффективен, однако он подразумевает что мы «знаем», где телефон, еще до того, как начали поиск.

Начальная установка третьего аргумента пустым списком означает, что мы начинаем поиск, имея чистый лист бумаги. Изменяя эту начальную установку, можно получить разные варианты поиска. Вопрос «найти телефону не заходя в комнаты d и f» можно выразить на Прологе так:

?- есть_телефон(X), переход (a,X,[d,f]).

В разд. 7.9 мы рассмотрим некоторые общие процедуры поиска по графу, в том числе программу, находящую кратчайший путь.

Упражнение 7.2. Допишите вышеприведенную программу так, чтобы она печатала такие сообщения, как «входим в комнату X» и «телефон найден в комнате Y», подставляя в них соответствующие номера комнат.

Упражнение 7.3. Может ли эта программа находить альтернативные пути? Если да, то где нужно «отсечь», чтобы избежать нахождения более чем одного пути?

Упражнение 7.4. Чем определяется порядок, в котором просматриваются комнаты?

7.3. Ханойские башни

Ханойские башни - это игра, в которой используются три штыря и набор дисков. Все диски различаются диаметром и нанизываются на штыри через отверстие в центре каждого диска. Первоначально все диски находятся на левом штыре. Цель игры состоит в том, чтобы переместить все диски на центральный штырь. Правый штырь можно использовать как «запасной» для временного размещения дисков. При каждом перемещении диска с одного штыря на другой должны соблюдаться два ограничения: перемещать можно только самый верхний диск на штыре, и, кроме того, нельзя ставить диск на другой диск меньшего размера.

Многим из тех людей, которые играют в эту игру, практически никогда не удается обнаружить весьма простую стратегию, позволяющую успешно играть в Ханойские башни с тремя штырями и N дисками. Чтобы не утомлять вас поисками решения этой задачи, мы откроем его:

• Граничное условие выполняется в случае, когда на исходном (левом) штыре нет дисков.

• Переместить N-1 дисков с исходного штыря на запасной (правый) штырь, используя итоговый штырь как запасной; отметим, что это перемещение осуществляется рекурсивно.

• Переместить один диск с исходного штыря на итоговый штырь. В этом месте наша программа будет выдавать сообщение об этом перемещении.

• Наконец, переместить N-1 дисков с запасного на итоговый, используя исходный штырь в качестве запасного.

Пролог-программа, реализующая данную стратегию, определяется следующим образом. Определяется предикат ханой с одним аргументом, такой, что xaной(N) означает выдачу сообщений о последовательности перемещений, когда на исходном штыре находится N дисков. Из двух утверждений предиката переместить один задает граничное условие, которое сформулировано выше, а второй – реализует рекурсивные случаи. Предикат переместить имеет четыре аргумента. Первый аргумент – это число дисков, которые нужно переместить. Три другие представляют исходный, итоговый и запасной штыри для перемещения дисков. Предикат сообщить использует предикат write для печати названий штырей, участвующих в перемещении диска.

xaной(N):- переместить(N, левый,средний,правый).

переместить(О,_,_,_):-!.

переместить(N, А,В,С):-М is N-1,переместить(М,А,С,В),сообщить(А,В), переместить(М,С,В,А).

сообщить(Х,Y):-write([переместили,диск,со,штыря,Х,на, штырь,Y]),nl.

7.4. Справочник комплектующих деталей

В главе 3 мы рассматривали программу, выдающую на печать список деталей, необходимых при сборке некоторого узла на основе справочника комплектующих деталей. В данном разделе мы усовершенствуем эту программу, будем учитывать количество деталей путем суммирования числа требуемых деталей по мере перехода от узлов к их составляющим. Кроме того, усовершенствованная программа правильно обрабатывает повторения; процедура собрать устраняет повторения при суммировании для каждой из требуемых деталей перед тем, как ответ выдается на печать.

Организация базы данных справочника сходна с тем, что описано в гл. 3. Сборочный узел представлен в виде списка структур вида чис(X, Y), где X – это имя некоторой детали (простой детали или узла), a Y – необходимое количество таких деталей. Ниже перечислены все предикаты измененной программы с указанием их назначения:

Деталиузла(А): выдает на печать список всех простых деталей, требующихся для сборки узла А, и количество каждой детали.

Деталиузлов(N,X,P): P - это список структур чис(Дет, Кол), где Дет - это название детали, а Кол - это количество таких деталей, требующихся для сборки каждого из экземпляров узлов X. N - целое, а X – атом, представляющий название некоторой детали.

Деталировка(N,S,Р): Р - это, как и выше, список структур чис, требующихся для сборки всех узлов, представленных элементами списка S; N задает число экземпляров списка S, N – целое; S – список структур чис.