【问题标题】:Missionaries and Cannibals - LISP传教士和食人者 - LISP
【发布时间】:2019-08-30 15:04:16
【问题描述】:

著名的传教士和食人族问题如下:

三个传教士和三个食人者在一条河的东边。他们有一艘足够大的船,最多可以载两个人。对于这两家银行来说,如果银行里有传教士,他们的数量就不会被食人族所超越,因为食人族会吃掉传教士。没有人在船上,船不能自己过河。传教士和食人族怎么能活着到达彼岸?

我选择将状态表示为包含五个元素的列表。第一个元素代表东岸传教士的人数;第二个代表东岸食人族的数量;第三个代表西岸传教士的人数;第四个代表西岸食人族的数量;第五个代表船的位置,可以是东也可以是西。在这种表示下,初始状态将表示为 (3 3 0 0 east)。

我的操作符是否正确?还是有另一种方法(使用我的状态表示)来定义问题运算符?

任何见解将不胜感激!

我的问题算子如下:

    (defparameter *operators*
     '(boat-takes-missionary-east
     boat-takes-cannibal-east
     boat-takes-missionary-west
     boat-takes-cannibal-west
     boat-takes-missionary-missionary-east
     boat-takes-missionary-cannibal-east
     boat-takes-cannibal-cannibal-east
     boat-takes-missionary-missionary-west
     boat-takes-missionary-cannibal-west
     boat-takes-cannibal-cannibal-West)
    )

【问题讨论】:

    标签: lisp common-lisp breadth-first-search


    【解决方案1】:

    在不深入细节的情况下,解决此问题的一种简单方法是称为 generate-and-test 的方法,您可以从初始状态生成所有可达状态并测试解决方案(或拒绝不需要的状态)。

    这是一种通用的前向搜索函数,接受一个初始状态,一个计算下一个状态列表的下一个函数(给定一个状态和一个当前“路径”),并将一个函数应用于每个访问过的状态。

    (defun forward-search (initial-state next function)
      (labels ((recurse (state path)
                 (funcall function state path)
                 (push state path)
                 (dolist (state (funcall next state path))
                   (recurse state path))))
        (recurse initial-state nil)))
    

    例如,这是一个从 0 开始的搜索,并且对于每个小于 5 的 v,可能的邻居状态是 v+1v+2

    (forward-search 0
                    (lambda (v &optional p)
                      (declare (ignore p))
                      (if (< v 5)
                          (list (+ v 1) (+ v 2))
                          nil))
                    (lambda (v path)
                      (declare (ignore v))
                      (print path)))
    

    轨迹如下,路径代表通向当前状态的所有中间状态(倒序):

    NIL
    (0)
    (1 0)
    (2 1 0)
    (3 2 1 0)
    (4 3 2 1 0)
    (4 3 2 1 0)
    (3 2 1 0)
    (2 1 0)
    (4 2 1 0)
    (4 2 1 0)
    (1 0)
    (3 1 0)
    (4 3 1 0)
    (4 3 1 0)
    (3 1 0)
    (0)
    (2 0)
    (3 2 0)
    (4 3 2 0)
    (4 3 2 0)
    (3 2 0)
    (2 0)
    (4 2 0)
    (4 2 0)
    

    您可以在 next 函数中使用 path 参数来拒绝路径中已经出现的状态(提示:您不想多次访问某个状态,因为您的情况很可能会出现这种情况)。

    您需要有一种方法来表示一个状态并计算下一个状态(请参阅其他答案)。

    【讨论】:

    • 到目前为止,在我的简短研究中,我还没有遇到过生成和测试方法。我一定会在将来重新访问您的评论。非常感谢您的宝贵时间!
    【解决方案2】:

    无论如何,您选择的运算符都需要分解,因此我宁愿直接使用该表示。此外,您不需要判断船是向东还是向西,因为它已经在各州进行了编码。您的操作员唯一需要知道的是运送了多少传教士和多少食人族。

    这意味着你可以用两个数字来表示状态转换:

    start state: (3 3 0 0 east)
    transiton: (1 1)
    end state: (2 2 1 1 west)
    

    【讨论】:

    • 我没有意识到船的位置被编码了。感谢您的洞察力!
    【解决方案3】:

    我首先要抽象状态:没有人需要关心它们的表示。状态的实现只需要三位信息,因为你只需要一侧的数字和船所在的一侧。

    编写一个函数来生成状态,接受双方的参数,并进行适当的健全性检查。使用关键字参数很有用,因为您可以使用提供的 -p 选项来了解您是否可以默认另一个参数或是否需要对其进行完整性检查。

    编写一个检查州合法性的函数:传教士会在州内被吃掉吗?你可以把它和前面的函数结合起来,最后得到一个叫做maybe-make-state的函数,它会返回一个状态,或者nil,如果传教士会被吃掉。

    写一个谓词告诉你一个状态是否是期望的结果。

    使用上述函数编写一个函数,给定一个状态,返回所有合法子状态的列表。

    现在,使用这些函数,您可以编写一个搜索算法来从一个起始状态进行搜索。该算法需要广度优先(为什么?)。

    【讨论】:

      猜你喜欢
      • 2019-07-06
      • 1970-01-01
      • 1970-01-01
      • 2021-04-25
      • 1970-01-01
      • 1970-01-01
      • 2016-05-27
      • 2014-12-14
      • 1970-01-01
      相关资源
      最近更新 更多