【问题标题】:Prolog: Shortest Path for KnightProlog:骑士的最短路径
【发布时间】:2017-05-03 09:41:42
【问题描述】:

嗨,所以在我被告知这个问题已经被问过很多次之前,我已经查看了一堆问题,但没有一个与 Prolog 相关。这就是我遇到的困难。

我试图找到棋盘上两点之间的最短路径。我拥有的代码专门用于骑士。到目前为止,这是我的代码:

move1( (X1,Y1), (X2,Y2) ) :- up1( X1, X2 ), up2( Y1, Y2 ).
move1( (X1,Y1), (X2,Y2) ) :- up2( X1, X2 ), up1( Y1, Y2 ).
move1( (X1,Y1), (X2,Y2) ) :- up1( X1, X2 ), down2( Y1, Y2 ).
move1( (X1,Y1), (X2,Y2) ) :- up2( X1, X2 ), down1( Y1, Y2 ).
move1( (X1,Y1), (X2,Y2) ) :- down1( X1, X2 ), up2( Y1, Y2 ).
move1( (X1,Y1), (X2,Y2) ) :- down2( X1, X2 ), up1( Y1, Y2 ).
move1( (X1,Y1), (X2,Y2) ) :- down1( X1, X2 ), down2( Y1, Y2 ).
move1( (X1,Y1), (X2,Y2) ) :- down2( X1, X2 ), down1( Y1, Y2 ).

up1( U, V ) :- successor( U, V ).
up2( U, W ) :- successor( U, V ), successor( V, W ).
down1( U, V ) :- up1( V, U ).
down2( U, V ) :- up2( V, U ).

successor( 1, 2 ).
successor( 2, 3 ).
successor( 3, 4 ).
successor( 4, 5 ).

edge((X1,Y1) , (X2,Y2)) :- move1( (X1,Y1), (X2,Y2) ).

path((X1,Y1), (X2,Y2),N,[(X1,Y1), (X2,Y2)]) :- N > 0, edge((X1,Y1), (X2,Y2)).
path((X1,Y1), (X3,Y3),N,[(X1,Y1)|P1]) :- N > 0, N1 is N-1, path((X2,Y2), (X3,Y3),N1,P1), edge((X1,Y1), (X2,Y2)), nonmember((X1,Y1),P1).

shortest((X1,Y1),(X2,Y2),P) :- path((X1,Y1),(X2,Y2),24,P),!.

visit((X1,Y1),P,N) :-  path((X1,Y1), (X2,Y2),N,P),N2 is N+1,len(P,N2).

len([],0).
len([_|T],N)  :-  len(T,X),  N is X+1. 

nonmember(X,[]).
nonmember(X,[U|Y]) :- X \= U, nonmember(X,Y).

如您所见,我只找到第一条路径而不是最短路径。我不确定如何在 prolog 中编写代码并找出获得所有最短路径的方法。我正在考虑列出所有可能的路径,然后通过并找到最短的路径,但我似乎无法编写代码。

findAll((X1,Y1),(X2,Y2),P,L) :- path((X1,Y1),(X2,Y2),24,P),length(P,L).

给我每条路径的长度,但我不确定如何处理它。 任何关于如何在 Prolog 中编码以找到最短路径的帮助都会非常有帮助,这就是我正在寻找的。

【问题讨论】:

    标签: prolog combinatorics shortest-path


    【解决方案1】:

    要遵循您似乎枚举所有路径并找到最小值的方法,我会使用 aggregate 库。

    但首先,我建议对您的代码进行一些清理,因为我不确定它是否能以这种方式正常工作(但我刚刚检查得很快)。

    特别是,您希望到达一个谓词path((X1, Y1),(X2, Y2),Path),该谓词将允许枚举所有路径(如果您重试多次)然后停止。一旦所有路径都用尽,你的似乎无限循环。 你可以找到一些关于如何枚举所有非循环路径here的启发。

    修复后,您可以通过以下方式使用aggregate/3

    :-use_module(library(aggregate)).    
    
    find_min(Start, End, Path) :-
        aggregate(
            min(Length, Path),
            (path(Start, End, Path), length(Path, Length)),
            min(_,P)
         ).
    

    find_min(Start, End, Path) 将允许您枚举从一个单元格到另一个单元格的所有最短路径。 注意StartEnd可能有多个最短路径;这将返回所有最短路径,而不仅仅是一条。

    另一个可能的解决方案是实现 Djikstra 的最短路径算法,这可能比枚举所有路径并找到最小值(就像我们在这里所做的那样)要高效得多。但那将是一种完全不同的方法。

    编辑:对于 4x4 或 5x5 的小板,枚举所有路径并找到最小方法可能会起作用,但在 8x8 板上,复杂性将变得难以控制。 在这里,最好的方法是改变您的方法并实施,例如 Djikstra 算法。

    【讨论】:

    • 你能用\+ memberchk代替你的nonmember谓词吗?
    • 是的,很抱歉我删除了我的 cmets,因为我有点想通了,如果我最后调用非成员,那么它可以工作,因为 P1 现在有值。另外,我尝试使用您上面提供的代码,但似乎无法正常工作。使用最短路径调用:shortest( (1,1), (4,4) , P )。它只是一直循环。
    • 通过我的代码,我注意到当我调用没有剪切的最短路径时,它从最短路径开始,并继续列出越来越长的路径。是否可以肯定地说我的代码首先找到最短路径然后继续寻找更长的路径?
    • 可能是这样,是的。路径谓词的第一个子句检查长度为 1 的路径;第二个检查长度为 n+1 的路径。因此,您正在逐步构建长度增加的路径。
    • 这也是它不终止的原因:递归调用 path 是你做的第一件事;您仅在之后检查路径是否有效。所以它会继续生成长度增加的路径,即使它们都应该由于例如循环而被丢弃。无论如何,我强烈建议您研究一下 Djikstra 的算法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-04-03
    • 1970-01-01
    • 2023-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-23
    相关资源
    最近更新 更多