使用Prolog中的广度优先搜索(BFS)解决食人族/传教士问题?

时间:2012-03-30 05:13:00

标签: prolog breadth-first-search river-crossing-puzzle

我正在努力解决经典的传教士(M)和食人族(C)问题,左岸的起始状态是3 M和3 C,右侧的目标状态是3M,3C。我已完成程序中的基本功能,我需要实现搜索策略,如BFS和DFS。

基本上我的代码是从互联网上学习的。到目前为止,我可以使用DFS方法成功运行程序,但我尝试使用BFS运行它总是返回false。这是我的第一个SWI-Prolog程序,我找不到代码的问题。

这是我的代码的一部分,希望你能帮我找到它的问题

solve2 :-
   bfs([[[3,3,left]]],[0,0,right],[[3,3,left]],Solution),
   printSolution(Solution).

bfs([[[A,B,C]]],[A,B,C],_,[]).
bfs([[[A,B,C]|Visisted]|RestPaths],[D,E,F],Visisted,Moves) :-
   findall([[I,J,K],[A,B,C]|Visited]),
     (
       move([A,B,C],[I,J,K],Description),
       safe([I,J,K]),
       not(member([I,J,K],Visited)
     ),
     NewPaths
   ),
   append(RestPaths,NewPaths,CurrentPaths),
   bfs(CurrentPaths,[D,E,F],[[I,J,K]|Visisted],MoreMoves),
   Moves = [ [[A,B,C],[I,J,K],Description] | MoreMoves ].


move([A,B,left],[A1,B,right],'One missionary cross river') :-
   A > 0, A1 is A - 1.  
   % Go this state if left M > 0. New left M is M-1
.
.
.
.
.
safe([A,B,_]) :-
   (B =< A ; A = 0),
   A1 is 3-A, B1 is 3-B,
   (B1 =< A1; A1 =0).

我使用findall在进入下一级之前找到所有可能的路径。只有一个传递safe()才会考虑下一个状态。如果状态已存在,则不会使用该状态。由于我的程序可以使用DFS运行,所以我认为move()和safe()谓词没有任何问题。我的BFS谓词正在根据我的DFS代码进行更改,但它不起作用。

6 个答案:

答案 0 :(得分:5)

如果深度优先搜索直接映射到Prolog的搜索,有一种非常简单的方法可以将深度优先搜索程序转换为广度优先搜索程序。这种技术称为iterative deepening

只需添加一个额外的参数即可确保搜索只会进行N步。 所以dfs-version:

dfs(State) :-
   final(State).
dfs(State1) :-
   state_transition(State1, State2),
   dfs(State2).

通过为深度添加参数将其转换为bfs。例如。使用

bfs(State, _) :-
   final(State).
bfs(State1, s(X)) :-
   state_transition(State1, State2),
   bfs(State2, X).

目标bfs(State,s(s(s(0))))现在可以找到需要3个或更少步骤的所有派生。你仍然可以执行dfs!只需使用bfs(State,X)

要查找所有派生,请使用natural_number(X), bfs(State,X)

通常使用列表而不是s(X) - 数字是有用的。此列表可能包含所有中间状态或执行的特定转换。

你可能会犹豫使用这种技术,因为它似乎重新计算了很多中间状态(“反复扩展状态”)。毕竟,首先它只用一步搜索所有路径,然后最多两步,然后最多三步......但是,如果你的问题是搜索问题,隐藏在state_transition/2内的分支因子将会减轻这种开销。要看到这一点,请考虑分支因子2:我们只需要两倍的开销!通常,有一些简单的方法可以重新获得两个因素:例如,加快state_transition/2final/1

但最大的优势是它不会消耗大量空间 - 与天真的dfs相比。

答案 1 :(得分:2)

Logtalk发行版包含一个示例&#34; search&#34;,它实现了状态空间搜索的框架:

https://github.com/LogtalkDotOrg/logtalk3/tree/master/examples/searching

&#34; classic&#34;问题包括(农民,传教士和食人族,拼图8,桥梁,水壶等)。一些搜索算法适用于(经许可)Ivan Bratko的书&#34; Prolog编程用于人工智能&#34;。该示例还包括一个性能监视器,可以为您提供有关搜索方法性能的一些基本统计信息(例如分支因子和状态转换次数)。该框架易于扩展,既适用于新问题,也适用于新的搜索方法。

答案 2 :(得分:1)

请参阅此要点以查看可能的解决方案,可能对您的问题有所帮助。

要点:solve Missionaries and cannibals in Prolog

答案 3 :(得分:1)

我先解决了深度优先问题,然后尝试了广度优先,尝试将可重用部分与状态搜索算法明确区分开来:

miss_cann_dfs :-
    initial(I),
    solve_dfs(I, [I], Path),
    maplist(writeln, Path), nl.

solve_dfs(S, RPath, Path) :-
    final(S),
    reverse(RPath, Path).
solve_dfs(S, SoFar, Path) :-
    move(S, T),
    \+ memberchk(T, SoFar),
    solve_dfs(T, [T|SoFar], Path).

miss_cann_bfs :-
    initial(I),
    solve_bfs([[I]], Path),
    maplist(writeln, Path), nl.

solve_bfs(Paths, Path) :-
    extend(Paths, Extended),
    (   member(RPath, Extended),
        RPath = [H|_],
        final(H),
        reverse(RPath, Path)
    ;   solve_bfs(Extended, Path)
    ).

extend(Paths, Extended) :-
    findall([Q,H|R],
        (   member([H|R], Paths),
            move(H, Q),
            \+ member(Q, R)
        ), Extended),
    Extended \= [].

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% problem representation
% independent from search method
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

initial((3,3, >, 0,0)).
final((0,0, <, 3,3)).

% apply a *valid* move
move((M1i,C1i, Bi, M2i,C2i), (M1f,C1f, Bf, M2f,C2f)) :-
    direction(Bi, F1, F2, Bf),
    who_move(MM, CM),
    M1f is M1i + MM * F1, M1f >= 0,
    C1f is C1i + CM * F1, C1f >= 0,
    ( M1f >= C1f ; M1f == 0 ),
    M2f is M2i + MM * F2, M2f >= 0,
    C2f is C2i + CM * F2, C2f >= 0,
    ( M2f >= C2f ; M2f == 0 ).

direction(>, -1, +1, <).
direction(<, +1, -1, >).

% valid placements on boat
who_move(M, C) :-
    M = 2, C = 0 ;
    M = 1, C = 0 ;
    M = 1, C = 1 ;
    M = 0, C = 2 ;
    M = 0, C = 1 .

我建议您以类似的方式构建代码,使用类似于extend / 2的谓词,以便明确何时停止搜索。

答案 4 :(得分:0)

如果您的Prolog系统有前向链接,您也可以解决 通过前向链规则对其进行建模的问题。这里 是如何解决Jekejeke Minlog中的水壶问题的一个例子。 状态由谓词状态/ 2表示。

您首先要提供一个过滤重复项的规则,如下所示。该 规则说应该删除传入的state / 2事实, 如果它已经在远期商店中:

% avoid duplicate state
unit &:- &- state(X,Y) && state(X,Y), !.

然后你给出规则,说明不需要继续搜索 当达到最终状态时。在本示例中,我们检查 其中一个容器含有1升水:

% halt for final states
unit &:- state(_,1), !.
unit &:- state(1,_), !.

作为下一步,将状态转换建模为正向链接 规则。这是直截了当的。我们模拟排空,填充和浇注 船只:

% emptying a vessel
state(0,X) &:- state(_,X).
state(X,0) &:- state(X,_).

% filling a vessel
state(5,X) &:- state(_,X).
state(X,7) &:- state(X,_).

% pouring water from one vessel to the other vessel
state(Z,T) &:- state(X,Y), Z is min(5,X+Y), T is max(0,X+Y-5).
state(T,Z) &:- state(X,Y), Z is min(7,X+Y), T is max(0,X+Y-7).

我们现在可以使用正向链接引擎为我们完成工作。它 不会做迭代深度,也不会先做宽度。 它只会通过一种贪婪的策略来解决单位问题 给定的事实和过程只完成,因为状态空间 是有限的。结果如下:

?- post(state(0,0)), posted.
state(0, 0).
state(5, 0).
state(5, 7).
state(0, 7).
Etc..

该方法将告诉您是否有解决方案,但不解释 解决方案。使其可解释的一种方法是使用事实 state / 4而不是事实状态/ 2。最后两个参数用于 操作列表和列表长度。

然后针对选择的规则更改避免重复的规则 最小的解决方案。内容如下:

% choose shorter path
unit &:- &- state(X,Y,_,N) && state(X,Y,_,M), M<N, !.
unit &:- state(X,Y,_,N) && &- state(X,Y,_,M), N<M.

然后我们得到:

?- post(state(0,0,[],0)), posted.
state(0, 0, [], 0).
state(5, 0, [fl], 1).
state(5, 7, [fr,fl], 2).
state(0, 5, [plr,fl], 2).
Etc..

有了一个小助手谓词,我们可以强制解释 导致路径的行为:

?- post(state(0,0,[],0)), state(1,7,L,_), explain(L).
0-0
fill left vessel
5-0
pour left vessel into right vessel
0-5
fill left vessel
5-5
pour left vessel into right vessel
3-7
empty right vessel
3-0
pour left vessel into right vessel
0-3
fill left vessel
5-3
pour left vessel into right vessel
1-7

再见

源代码:水壶状态
http://www.xlog.ch/jekejeke/forward/jugs3.p

源代码:水壶状态和路径
http://www.xlog.ch/jekejeke/forward/jugs3path.p

答案 5 :(得分:0)

如果仍然有人对python解决方案感兴趣,请查找以下内容。 为了简化起见,只考虑了左侧的传教士和食人族。

这是解决方案树。 solution tree

#M #missionaries in left
#C #cannibals in left
# B=1left
# B=0right

def is_valid(state):
    if(state[0]>3 or state[1]>3 or state[2]>1 or state[0]<0 or state[1]<0 or state[2]<0 or (0<state[0]<state[1]) or (0<(3-state[0])<(3-state[1]))):
        return False
    else:
        return True

def generate_next_states(M,C,B):
    moves = [[1, 0, 1], [0, 1, 1], [2, 0, 1], [0, 2, 1], [1, 1, 1]]
    valid_states = []
    for each in moves:
        if(B==1):next_state = [x1 - x2 for (x1, x2) in zip([M, C, B], each)]
        else:next_state = [x1 + x2 for (x1, x2) in zip([M, C, B], each)]
        if (is_valid(next_state)):
            # print(next_state)
            valid_states.append(next_state)
    return valid_states
solutions = []
def find_sol(M,C,B,visited):
    if([M,C,B]==[0,0,0]):#everyne crossed successfully
        # print("Solution reached, steps: ",visited+[[0,0,0]])
        solutions.append(visited+[[0,0,0]])
        return True
    elif([M,C,B] in visited):#prevent looping
        return False
    else:
        visited.append([M,C,B])
        if(B==1):#boat is in left
            for each_s in generate_next_states(M,C,B):
                find_sol(each_s[0],each_s[1],each_s[2],visited[:])
        else:#boat in in right
            for each_s in generate_next_states(M,C,B):
                find_sol(each_s[0],each_s[1],each_s[2],visited[:])


find_sol(3,3,1,[])

solutions.sort()
for each_sol in solutions:
    print(each_sol)