Как найти уникальный кратчайший путь для взвешенного ориентированного графа с помощью SWI Prolog?

это расширение того, что я сделал в классе Prolog. В классе меня попросили написать предикат path(X, Y, N) который возвращает true, если и только есть путь от node X в узле Y длиной N. Приведен список направленных ребер с соответствующими весами, например edge(a, b, 3) и edge(c, d, 10).

данная проблема довольно проста (только некоторые рекурсии и базовые случаи). Тем не менее, я подумал, что, возможно, я мог бы расширить это немного дальше. С учетом что простой направленный вход графа может содержать циклы и содержит только неотрицательные веса, какова длина уникальный кратчайший путь из заданных узлов A и B. (By уникальный, я имею в виду, что этот предикат должен возвращать false, если существует более одного кратчайшего пути из A to B).

вот пример базы данных, которая содержит цикл (a, b, c, e, a).

edge(a, b, 1).
edge(b, c, 2).
edge(c, d, 1).
edge(b, d, 4).
edge(c, e, 1).
edge(e, a, 10).
edge(e, d, 6).

я думал, что для удовлетворения the уникальный условие, я думал, что я должен увеличить оригинальный path/3 предикат также содержит информацию о пути в виде списка (чтобы я мог сравнить уникальность пути позже). Это новое увеличение отражается в новом path/4 сказуемое.

path(X, X, 0, []).
path(X, Y, N, [Y]) :- edge(X, Y, N).
path(X, Y, N, [Z|T]) :- edge(X, Z, N1), N2 is N - N1, N2 > 0, path(Z, Y, N2, T).

path(X, Y, N) :- path(X, Y, N, _).

начиная с этого кода, я уже нашел проблему: если я попытаюсь объединить предикат с path(a, b, N, Z), это не будет работать, потому что N не сможет объединиться с N2 is N - N1. Однако, если я изменю это часть N is N1 + N2, это все равно не сработает, потому что N2 по-прежнему не унифицирован. Если я изменю всю строку предиката на:

path(X, Y, N, [Z|T]) :- edge(X, Z, N1), path(Z, Y, N2, T), N is N1 + N2.

это будет работать бесконечно, потому что количество путей, возможно, бесконечно, потому что график может содержать петли (которые я хочу попытаться сохранить таким образом, как вызов).

что касается shortestpath/3 предикат, я не могу найти все пути и проверить, все ли пути длиннее, потому что количество путей может быть бесконечным из - за имея цикл. Вместо этого я попытался найти любые пути, которые имеют длину между 0 и заданным N; если нет пути, то это определенно самый короткий путь.

countdown(N, N).
countdown(N, X) :- N1 is N - 1, N1 >= 0, countdown(N1, X).

shortestpath(A, B, N) :- path(A, B, N), +((countdown(N, N1), N > N1, path(A, B, N1))).

однако это не касается N заданный как переменная (потому что функция обратного отсчета не будет работать), не говоря уже о уникальный ограничения.

Итак, мой вопрос в том, есть ли способ заставить этот вопрос работать или это действительно невозможно сделать? Если есть такой решение, Пожалуйста, любезно предоставьте его здесь (или если вы думаете, что это вопрос "домашнее задание", пожалуйста, по крайней мере, направьте меня в правильном направлении).

ограничения:

  • я не хочу использовать встроенные предикаты. Только "простые" или "основные" предикаты, такие как +, is, +, например. var, nonvar, asserta и подобные предикаты также несколько приемлемы (поскольку нет альтернативы, которая достигает та же функциональность).

  • я хочу, чтобы он был как можно более общим; то есть любые аргументы для предиката должны быть в состоянии дать как переменную. (или, по крайней мере последний аргумент shortestpath/3, что является длиной кратчайшего пути, переменной).


я уже просмотрел следующие вопросы, и это не отвечает на мою ситуацию:

пожалуйста, не стесняйтесь указывать мне на любой другой вопрос, который касается моего вопроса.

1 ответов


приятно получить вопрос, вдохновленный домашней работой, а не только фактической домашней работой! Давайте начнем с вашего предиката и посмотрим, сможем ли мы заставить его подчиниться, а затем поговорим о некоторых альтернативных подходах.

сначала я начинаю с упрощенного предиката из вашего:

path(X, Y, N, [X-Y]) :- edge(X, Y, N).
path(X, Z, N, [X-Y|T]) :-
    edge(X, Y, N0),
    path(Y, Z, N1, T),
    N is N0 + N1.

основное отличие здесь в том, что я просто генерирую пути, а затем вычисляю длины. Я не занимаюсь вычитанием. Это распространено в Prolog, чтобы начать с простейший подход к генерации и тестированию, а затем уточните либо генератор, либо тест, либо и то, и другое, пока вы не будете счастливы, так что это просто очень простой генератор. Я держу как исходный, так и конечный узлы в последовательности пути, чтобы помочь мне визуализировать, что происходит, и с ним вы сразу видите проблему с циклами:

?- path(a, e, N, T).
N = 4,
T = [a-b, b-c, c-e] ;
N = 18,
T = [a-b, b-c, c-e, e-a, a-b, b-c, c-e] ;
N = 32,
T = [a-b, b-c, c-e, e-a, a-b, b-c, c-e, e-a, ... - ...|...] .

я не думаю, что мы с вашим образцом графика, но мы могли бы немного страдать здесь от глубины первого поиска пролога: до тех пор, пока нет неудача, у Prolog нет причин создавать резервную копию и пробовать другой путь. И вы видите циклы прямо здесь. Если бы вместо этого он использовал поиск по ширине, вы были бы уверены, что первое решение является самым коротким просто потому, что, продвигая все на один шаг, вы никогда не попадаете в кроличью нору до создания своего первого решения. Алгоритм Дейкстры (спасибо за напоминание @JakobLovern) обходит проблему, окрашивая посещенные узлы и не считая их более одного раза.

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

path(X, Y, N, Path) :- path(X, Y, N, [], Path).

path(X, Y, N, Seen, [X]) :-
    \+ memberchk(X, Seen),
    edge(X, Y, N).
path(X, Z, N, Seen, [X|T]) :-
    \+ memberchk(X, Seen),
    edge(X, Y, N0),
    path(Y, Z, N1, [X|Seen], T),
    \+ memberchk(X, T),
    N is N0 + N1.

добавлять