Скачиваний:
53
Добавлен:
01.05.2014
Размер:
565.76 Кб
Скачать

7.3. Порядок целей

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

Обсудим сначала порядок целей в случае программирования баз данных. Порядок целей может повлиять на последовательность решений. Рассмотрим вопрос дочь (X,аран )?. относительно видоизмененной программы 1.2, в которой переставлены факты женщина(милка) и женщина(иска). Мы получим два решения в следующем порядке: Х = милка, Х = иска. Если теперь поменять порядок целей в правиле, задающем отношение дочь, т.е. дочь(Х,У)женщина (Х), omeu(Y.X), то порядок решений, даваемых той же самой базой данных, будет: Х = иска, Х = милка.

Причина, по которой порядок целей в теле предложения влияет на порядок решений, отличается от причины, по которой порядок правил в процедуре влияет на порядок решений. Изменение порядка правил не изменяет дерево поиска, которое используется при решении данной цели. Просто обход дерева производится в ином порядке. Изменение порядка целей приводит к изменению дерева поиска.

Порядок целей определяет дерево поиска.

Порядок целей влияет на количество проверок, выполняемых программой при решении вопроса, так как порядок целей определяет дерево поиска для обхода. Рассмотрим два дерева поиска решения вопроса сын (X.аран)?, приведенные на рис. 5.2. Они соответствуют двум разным способам поиска решений. В первом случае выбираются все дети человека аран и проверяется, являются ли они мужчинами. Второй случай соответствует перестановке целей в теле правила, задающего отношение сын, т.е. правилу сын(Х,Y)мужчина(Х), родитель (Y,X). Теперь при решении вопроса просматриваются все мужчины и проверяются, являются ли они детьми человека аран. Если в программе имеется много фактов, относящихся к предикату мужчина, то объем поиска будет велик. Для других вопросов, например для вопроса сын(сара, X)?, данный порядок будет предпочтительнее. Поскольку сара не является мужчиной, поиск завершится быстрее.

В зависимости от того, как используется программа, тот или иной порядок целей является наилучшим. Рассмотрим определение отношения дедушка_или_бабушка. Имеются две возможности:

дедушка_или_бабушка(Х,Y)  родитель(Х.Y), родитель(Y,Z).

дедушка_или_бабушка(Х,Z)  родитель(Y,Z), родитель(Х,Y).

Если данное отношение использовать для поиска внуков, например, с помощью вопроса дедушка_или_бабушка (авраам,Х)?, то первое правило ускоряет поиск. Если же ищутся чьи-то предки с помощью такого вопроса, как дедушка_или_ бабушка(Х,исаак)?, то поиск ускоряется при использовании второго правила. Если эффективность имеет существенное значение, то можно посоветовать ввести два различных отношения – дедушка_или_бабушка и внук, которые пользователь будет применять по своему усмотрению.

В зависимости от порядка целей (в отличие от порядка правил) вычисления могут быть конечными или бесконечными. Рассмотрим рекурсивное правило

предок(Х,Y)  родитель(Х,Z), предок(Z,Y).

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

Порядок целей играет важную роль и в рекурсивном предложении алгоритма быстрой сортировки (программа 3.22):

sort([X | Xs], Ys) 

partition(Xs,X,Ls,Bs),

sort(Ls,Ls1),

sort(Bs.Bs1),

append(Ls1,[X | Bs1],Ys).

Список следует разбить на две меньшие части перед рекурсивной сортировкой частей. Если, например, поменять порядок целей partition и рекурсивной сортировки, то ни одно вычисление не завершится.

Рассмотрим теперь программу обращения списка (программа 3.16):

reverse([X | Xs),Zs)  reverse(Xs,Ys), append(Ys,[X],Zs),

reverse([ ],[ ]).

Существен не порядок правил, а порядок целей. Программа будет завершающейся, если первый аргумент цели - полный список. Цели, у которых первый аргумент - неполный список, приводят к бесконечным вычислениям. Если в рекурсивном правиле цели переставить, то завершение вычислений будет определяться вторым аргументом. Решение цели reverse, у которой второй аргумент - полный список, приведет к завершению вычисления. Вычисление не завершится, если второй аргумент - неполный список.

Менее очевидный пример возникает при рассмотрении отношения sublist, определенного как суффикс префикса, в терминах двух append-целей (программа 3.14(е)). Рассмотрим вопрос sublist([2,3][1,2,3,4])? относительно данной программы. Данная цель сводится к вопросу append(AXs,Bs,[l,2,3.4]), аррепd(Аs[2,3],АХs)?. Возникает конечное дерево поиска, и исходная цель успешно решается. Если в программе 3.14(е) цели переставить, то исходный вопрос сведется к вопросу append(As,[2,3],AXs), append(AXs,Bs,[I,2,3,4])?. Из-за первой цели возникнет незавершающееся вычисление, подобное приведенному на рис, 5.4.

Для рекурсивных программ, содержащих проверки (такие, как арифметическое сравнение или неравенство констант), можно привести полезное эвристическое правило определения порядка целей. Правило состоит в том, что проверки следует выполнять как можно раньше. Примером является программа для отношения partition- фрагмент программы 3.22. Первое рекурсивное правило имеет вид

Partitiont([X | Xs],Y,[X | Ls],Bs)  X  Y, partition(Xs,Y,Ls,Bs).

Проверку X  Y следует выполнить до рекурсивного вызова. Это приводит к сокращению дерева поиска.

При программировании на Прологе (возможно, в отличие от обычных жизненных ситуаций) наша цель состоит в наиболее быстром достижении неудачи. Неудачи сокращают дерево поиска и быстрее приводят нас к правильному решению.

Упражнения к разд. 7.3

1. Рассмотрите порядок целей программы 3.14(d), определяющей подсписок списка как суффикс некоторого префикса. В чем достоинство выбранного в программе 3.14(d) порядка append-целей?

2. Рассмотриге порядок предложений, порядок целей и условия остановки программы, задающей отношение substitute [см. упражнение 3.3(1)].

7.4. Избыточные решения

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

Наличие избыточных решений из-за возвратов может вызвать в предельном случае экспоненциальный рост времени работы программы. При решении конъюнкции п целей, каждая из которых имеет одно избыточное решение, в случае возвратов может быть порождено 2 решений, что приводит к изменению оценки времени работы программы от полиномиальной (или даже линейной) к экспоненциальной.

Одна из причин появления избыточных решений в программах на Прологе состоит в наличии различных правил, пригодных для одного и того же случая. рассмотрим следующие два предложения, определяющие отношение minimum:

minimum(X,Y,X)XY.

minimum(X,Y,Y)YX.

Вопрос minimum (2,2,M)? при использовании этих двух правил имеет единственый ответ М=2, который будет найден дважды. Таким образом, одно решение избыточное.

Аккуратное определение отношения позволяет избежать подобной ситуации.Второе предложение можно заменить на

minimum(X,Y,Y)  Y < X.

Теперь в случае, когда два числа равны, применимо лишь первое правило. Такую же аккуратность следует соблюдать и при определении отношения partition как части программы быстрой сортировки (программы 3.22). Программист должен быть уверен, что в случае равенства сравниваемого числа и числа, определяющего разбиение, применяется лишь одно рекурсивное правило.

Другая причина, приводящая к избыточным решениям, состоит в рассмотрении слишком большого числа специальных случаев. Иногда подобное рассмотрение мотивируется стремлением к эффективности. К программе 3.15, определяющей отношение append, можно добавить дополнительный факт, а именно append (Xs,[ ],Xs), что позволяет сократить рекурсивные вычисления, когда второй аргумент - пустой список. Для исключения избыточных решений каждое из остальных предложений программы должно применяться лишь к спискам с ненулевым вторым аргументом.

Поясним это на примере построения программы 7.2 для отношения merge(Xs,Ys,Zs), которое истинно, если слияние списков Xs и Ys из упорядоченных по возрастаниюцелых чисел приводит купорядоченному списку Zs.

merge(Xs, Ys.Zs)

упорядоченный список целых чисел Zs получен слиянием упорядоченных списков Xs

и Ys.

merge([X [ Xs],[Y | Ys],[X | Zs])

X<Y,merge(Xs,[Y | Ys],Zs).

megre([X | Xs], [Y | Ys], [X, X | Zs])

X = Y, merge (Xs, Ys.Zs).

merge([X | Xs], [Y | Ys], [Y 1 Zs]) 

X>Y,merge([X | Xs],Ys,Zs).

merge([ ],[X | Xs],[X | Xs]).

merge(Xs,[ ],Xs).

Программа 7.2. Слияние упорядоченных списков.

Имеются три самостоятельных рекурсивных предложения, покрывающие три возможных случая: голова первого списка меньше, равна или больше головы второго списка. Предикаты <, = и > будут обсуждаться в гл.8. Следует рассматривать два случая, соответствующие исчерпанию каждого из списков. Заметьте, что мы позаботились и о том, чтобы цель merge([ ],[ ],[ ]) соответствовала лишь одному, последнему факту.

Избыточные вычисления возникают при использовании отношения member для определения того, входит ли некоторый элемент в некоторый список и существует ли несколько вхождений проверяемого элемента в этот список. Например, дерево поиска цели member(a,[a,b,a,c]) содержит две вершины, соответствующие решению.

Избыточные решения в предыдущих программах устранялись с помощью тщательного изучения логики программы. К программе member этот способ не применим. Для изменения поведения программы необходимо создать модифицированную версию программы member:

member_check{X,Xs)

X является элементом списка Xs.

member_check(X,[X | Xs]).

member.check(X,[Y | Ys])  XY, member_check (X,Ys).

Программа 7.3. Проверка вхождения в список.

Программа 7.3 определяет отношение member_check (Х,Хs), которое проверяет, входит ли Х в качестве элемента в список Xs. Программа получена из программы 3.12, задающей отношение member, добавлением проверки в рекурсивное правило. Значение программы не изменилось, но данная программа, рассматриваемая как программа на Прологе, работает иначе. Рис. 7.2, содержащий деревья поиска одной и той же цели этими двумя программами, демонстрирует различие последних. Левое дерево соответствует поиску цели memher(a,[a,b,a,c]) в прог-

Рис. 7.2. Различные деревья поиска.

рамме 3.12. Отметим, что в дереве существуют две вершины, соответствующие решению. Правое дерево соответствует поиску цели member_check(a,[a,b,a,c]) в программе 7.3, и в нем имеется лишь одна вершина, соответствующая решению.

Мы ограничили использование программы 7.3 для поиска целей случаями, когда оба аргумента - основные термы. Это объясняется реализацией отношения  в Прологе, обсуждаемой в разд. 11.3.

Соседние файлы в папке 1-13