【问题标题】:How to find the unique shortest path for a weighted directed graph with SWI Prolog?如何使用 SWI Prolog 找到加权有向图的唯一最短路径?
【发布时间】:2018-05-26 12:18:19
【问题描述】:

这是我在 Prolog 课程中所做的扩展。在课堂上,我被要求编写一个谓词path(X, Y, N),当且仅存在从节点X 到节点Y 且长度为N 的路径时返回true。给出的是具有相应权重的有向边列表,例如edge(a, b, 3)edge(c, d, 10)

给定的问题非常简单(只有一些递归和基本情况)。但是,我想也许我可以进一步扩展它。鉴于简单的有向图输入可能包含循环并且仅包含非负权重,那么从给定节点 AB唯一最短路径 的长度是多少。 (通过unique,我的意思是如果从AB 存在多个最短路径,则该谓词应返回false。

这是一个包含循环(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).

我认为为了满足 unique 条件,我认为应该增加原始的 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+varnonvarasserta 和类似的谓词在某种程度上也是可以接受的(因为没有替代品可以实现相同的功能)。

  • 我希望它尽可能通用;也就是说,谓词的任何参数都应该能够作为变量给出。 (或者至少有shortestpath/3的最后一个参数,也就是最短路径的长度,一个变量)。


我已经查看了以下问题,但它并没有回答我的情况:

请随时向我指出解决我问题的任何其他问题。

【问题讨论】:

  • 您能否提供一个包含循环的数据库的简单示例,用于测试...?
  • 可以做到,而且你的做法没有错。在某些方面,你让自己的生活变得比你不得不做的更难; Prolog 的解析算法是深度优先的,而你正在与之抗争,再加上只想返回一个独特的解决方案,最终只是一个卑鄙的小转折。但这是一个绝对可以解决的有趣问题;我敢肯定,其他伟大的头脑已经在研究答案。
  • 看看Dijkstra's algorithm。它字面意思就是为此而生的。

标签: prolog unique graph-theory shortest-path


【解决方案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 的深度优先搜索中受到一些影响:只要没有失败,Prolog 就没有理由备份并尝试另一条路径。你会看到那里的循环。如果它改用广度优先搜索,您将相当肯定第一个解决方案是最短的,因为通过将所有内容推进一步,您在生成第一个解决方案之前永远不会陷入困境。 Dijkstra 的算法(感谢@JakobLovern 的提醒)通过对访问的节点进行着色并且不多次计算它们来解决这个问题。

可以通过创建元解释器来控制搜索行为,这并不像听起来那么糟糕,但比调整搜索以适应循环需要更多的工作,我认为这是大多数人在这种情况下所做的一个图表,所以让我们先尝试一下:

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.

添加Seen 参数并使用\+ memberchk/2 来避免向路径中添加已经在路径中的东西并不是一件不常见的事情。 memberchk/2 不是 ISO,但它是一个非常常见的谓词。您可以像这样自己实现它(请不要!):

memberchk(X, L) :- once(member(X, L)).
member(X, [X|_]).
member(X, [_|Xs]) :- member(X, Xs).

我认为值得注意的是memberchk/2 + 列表等于 Dijkstra 算法中使用的基本集合。这就像 Python 中的 in;如果至少没有member/2,在 Prolog 中尝试做任何真实的事情会有点疯狂。

这些更改使path/4 避免了循环,因此您现在可以找到所有解决方案而没有任何虚假的解决方案。 注意:我没有让你的图表成为非循环的。我只是让path/4 意识到循环。

请注意,我们有多种解决方案:

?- path(a, d, X, Y).
X = 5,
Y = [a, b] ;
X = 4,
Y = [a, b, c] ;
X = 10,
Y = [a, b, c, e] ;
false.

有一个不错的库,aggregate,它对这种情况很有帮助。但是,您要求不提供虚假库。 :)

我们只求最短路径:

uniq_shortest_path(X, Y, MinCost, Path) :-
    path(X, Y, MinCost, Path), 
    \+ (path(X, Y, LowerCost, OtherPath), 
        OtherPath \= Path, 
        LowerCost =< MinCost).

这字面意思是Path 是 X 和 Y 之间唯一的最短路径(恰好具有成本 MinCost)当且仅当没有其他路径的成本小于或等于我们的成本。试试看:

?- uniq_shortest_path(a, d, MinCost, Path).
MinCost = 4,
Path = [a, b, c] ;

这个技巧并不便宜;它很可能通过将所有解决方案相互比较来起作用。但它确实有效,没有任何额外的恶作剧。

获得所有解决方案,按成本排序,然后在报告第一个解决方案的成本和路径之前确保前两个解决方案的成本不同,可能会带来显着的改进。

通过直接实现 Dijkstra 算法或尝试制作广度优先的元解释器,可能会发现更大的改进。进行迭代深化的方法可能会奏效,但我怀疑它会表现得更好,因为它经常不得不做并重新做所有工作,导致结果被修剪为太很贵。

无论如何,我希望这会有所帮助!对 Prolog 保持兴奋!

【讨论】:

    猜你喜欢
    • 2016-06-25
    • 2013-04-03
    • 1970-01-01
    • 1970-01-01
    • 2014-12-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-19
    相关资源
    最近更新 更多