Разберите этот фрагмент на Prolog и объясните поведение при запросе ancestor(alice, Y), возможные дубли и рекурсию; предложите способы оптимизации поиска и ограничения экспоненциального роста: parent(alice, bob).\nparent(bob, charlie).\nancestor(X,Y) :- parent(X,Y).\nancestor(X,Y) :- parent(X,Z), ancestor(Z,Y).

10 Окт в 13:32
4 +1
0
Ответы
1

Код:
parentalice,bobalice, bobalice,bob.
parentbob,charliebob, charliebob,charlie.
ancestorX,YX,YX,Y :- parentX,YX,YX,Y.
ancestorX,YX,YX,Y :- parentX,ZX,ZX,Z, ancestorZ,YZ,YZ,Y.

1) Что делает этот кусок

parent/2 — факты: alice — родитель bob, bob — родитель charlie.ancestor/2 — рекурсивное правило: Y — потомок X, если либо X — прямой родитель Y перваяальтернативапервая альтернативаперваяальтернатива, либо существует Z такой, что X — родитель Z и Z — предок Y втораяальтернативавторая альтернативавтораяальтернатива.

2) Поведение при запросе ?- ancestoralice,Yalice, Yalice,Y.
Prolog применит поиск по порядку глубинныйпоисксвозвратамиглубинный поиск с возвратамиглубинныйпоисксвозвратами:

Попытается первая альтернатива: ancestorX,YX,YX,Y :- parentX,YX,YX,Y.
X = alice, parentalice,Yalice, Yalice,Y даёт Y = bob. Это первое решение: Y = bob.При дальнейшем backtracking прямых parentalice,?alice, ?alice,? больше нет.Переходит ко второй альтернативе: ancestorX,YX,YX,Y :- parentX,ZX,ZX,Z, ancestorZ,YZ,YZ,Y.
берёт parentalice,Zalice,Zalice,Z → Z = bob, затем вызывает ancestorbob,Ybob,Ybob,Y.внутри ancestorbob,Ybob,Ybob,Y сначала проверяет parentbob,Ybob,Ybob,Y → Y = charlie. Это второе решение: Y = charlie.далее для ancestorbob,Ybob, Ybob,Y нет других parentbob,?bob, ?bob,?, пробует вторую альтернатива — нет Z, приводящего к новым результатам.нет других Z для parentalice,Zalice,Zalice,Z, поэтому поиск заканчивается.

Итого при этом примере ответы будут в порядке: Y = bob ; Y = charlie.

3) Дубли, рекурсия и зацикливание

Дубликаты: если существуют разные пути из X в один и тот же Y разныепромежуточныеZразные промежуточные ZразныепромежуточныеZ, то Prolog вернёт Y несколько раз поодномурешениюнакаждыйпутьпо одному решению на каждый путьпоодномурешениюнакаждыйпуть. Пример:
parenta,ba,ba,b. parenta,ca,ca,c. parentb,db,db,d. parentc,dc,dc,d.
Тогда ?- ancestora,Ya,Ya,Y. вернёт Y = b ; Y = c ; Y = d ; Y = d. dдважды—черезbичерезcd дважды — через b и через cdдваждычерезbичерезc.Зацикливание: если граф родителей содержит цикл примерparent(a,b).parent(b,a).пример parent(a,b). parent(b,a).примерparent(a,b).parent(b,a)., то чистая рекурсия может привести к бесконечному погружению бесконечныйпоискбесконечный поискбесконечныйпоиск и никогда не вернёт все решения. В приведённом исходном примере циклов нет, поэтому поиск завершается.

4) Ограничение экспоненциального роста и оптимизации
Ниже — проверенные приёмы, чтобы уменьшить дубли, избежать бесконечных циклов и сократить избыточную рекомпутацию.

a) Устранение дубликатов на уровне результатов

Получить уникальные результаты: setof/3 или bagof + sort:
setofY,ancestor(alice,Y),YsY, ancestor(alice,Y), YsY,ancestor(alice,Y),Ys. % вернёт упорядоченный список уникальных YИли собрать все и удалить повторы: findallY,ancestor(alice,Y),LY, ancestor(alice,Y), LY,ancestor(alice,Y),L, sortL,UniqueL, UniqueL,Unique.

b) Отсечение лишних ветвей cutcutcut, но осторожно

Можно локально использовать ! чтобы избежать лишних альтернатив, но cut изменяет семантику и может отбросить корректные решения, если неправильно применён.

c) Защита от циклов — "visited" аккумулятор

Добавить параметр посещённых вершин, чтобы не заходить в уже просмотренные:
ancestorX,YX,YX,Y :- ancestorX,Y,[X]X,Y,[X]X,Y,[X].
ancestor(X,Y,_) :- parentX,YX,YX,Y.
ancestorX,Y,VisitedX,Y,VisitedX,Y,Visited :-
parentX,ZX,ZX,Z,
+ memberZ,VisitedZ, VisitedZ,Visited,
ancestorZ,Y,[Z∣Visited]Z,Y,[Z|Visited]Z,Y,[ZVisited].Это предотвращает бесконечные циклы в графе.

d) Таблинг / мемоизация рекомендуюрекомендуюрекомендую

В современных Prolog'ах XSB,SWI−Prologидр.XSB, SWI-Prolog и др.XSB,SWIPrologидр. есть табличное вычисление tablingtablingtabling. Оно:
предотвращает бесконечное зацикливание для многих левых рекурсий,сохраняет уже вычисленные ответы и повторно использует их убираетэкспоненциальноеповторениеубирает экспоненциальное повторениеубираетэкспоненциальноеповторение.В SWI-Prolog:
:- table ancestor/2.
Это часто самый простой и эффективный способ для транситивных замыканий reachabilityreachabilityreachability.В системах без таблинга можно реализовать кеширование вручную с динамическим предикатом assert/retractassert/retractassert/retract, но это более хрупко.

e) Порядок литералов и неудачные варианты

Очень важен порядок в теле правила: ancestorX,YX,YX,Y :- parentX,ZX,ZX,Z, ancestorZ,YZ,YZ,Y. Здесь рекурсивный вызов стоит последним хвостоваярекурсияхвостовая рекурсияхвостоваярекурсия — это позволяет компиляторам выполнять last-call optimization и не накапливать лишний стек при глубоком, но линейном пути.Перестановка: ancestorX,YX,YX,Y :- ancestorZ,YZ,YZ,Y, parentX,ZX,ZX,Z. — приведёт к леворекурсивному варианту и часто к бесконечному рекурсивному вызову нереализуемосDFSнереализуемо с DFSнереализуемосDFS, поэтому порядок литералов критичен.

f) Альтернативы поиска: ширина, итеративное углубление

Если нужен ближайший предок минимальнаядлинапутиминимальная длина путиминимальнаядлинапути, глубинный поиск не гарантирует кратчайший путь. Можно реализовать поиск в ширину BFSBFSBFS или итеративное углубление, но это сложнее реализовать в стандартном Prolog; таблинг также может помочь с поиском кратчайших путей если дополнить взвешивание/ограничения.

g) Индексация фактов

Современные Prolog-системы индексируют аргументы предикатов чащепервый,иногдапервыедвачаще первый, иногда первые двачащепервый,иногдапервыедва. При частых запросах ancestoralice,...alice, ...alice,... важно, чтобы parent/2 имел индекс по первому аргументу — стандартные реализации это делают, что делает parentalice,Zalice, Zalice,Z быстрым.

5) Резюме рекомендаций

Для простых ацикличных деревьев текущая программа адекватна и выдаст корректные ответы.Чтобы убрать дубли: собирать результаты setof/sort.Чтобы избежать циклов и экспоненциального роста в общем графе: использовать таблинг :−tableancestor/2:- table ancestor/2:tableancestor/2 — лучший вариант; либо явный список посещённых вершин.Проверяйте порядок литералов: рекурсивный вызов в конце — хорошо tail−calltail-calltailcall; леворекурсия может приводить к не-терминированию.Для больших баз данных убедитесь, что parent/2 индексируется и используйте таблинг или кеширование.

Если хотите, могу:

показать конкретный пример с циклом и как visited предотвращает бесконечность;продемонстрировать использование :- table ancestor/2 в SWI-Prolog;или показать, как убрать дубликаты конкретным примером setof/3.
10 Окт в 14:12
Не можешь разобраться в этой теме?
Обратись за помощью к экспертам
Гарантированные бесплатные доработки в течение 1 года
Быстрое выполнение от 2 часов
Проверка работы на плагиат
Поможем написать учебную работу
Прямой эфир