传教士与野人过河问题
360公司 2012年校园招聘会笔试题算法题
传教士和野人问题(Missionaries and Cannibals)
这是一个经常在有关讨论人工智能的书籍中见到的问题, 其描述是这样的:
有N个传教士和N个野人来到河边渡河, 河岸有一条船, 每次至多可供k人乘渡。问传教士为了安全起见, 应如何规划摆渡方案, 使得任何时刻, 河两岸以及船上的野人数目总是不超过传教士的数目(否则不安全, 传教士有可能被野人吃掉)。即求解传教士和野人从左岸全部摆渡到右岸的过程中, 任何时刻满足M(传教士数)≥C(野人数)和M+C≤k的摆渡方案。
我们此处举例 , 只讨论N为3、k为2的乘渡问题, 这样传教士和野人问题的描述就具体为如下:
三个传教士与三个野人来到河边, 有一条船可供一人或两人乘渡, 问题是如何用这条船渡河才能使得河的任一岸上野人的数目总不超过传教士的数目(当然, 如果某一岸上只有野人而没有传教士是允许的)。
我们用一个三元组(m c b)来表示河岸上的状态, 其中m、c分别代表某一岸上传教士与野人的数目, b=1表示船在这一岸, b=0则表示船不在。
设N=3, k=2, 则给定的问题可用下图表示, 图中L和R表示左岸和右岸, B=1或0分别表示有船或无船。
约束条件是: 两岸上M≥C, 船上M+C≤2。
我们采用产生式系统来解决这一问题。由于传教士与野人的总数目是一常数, 所以只要表示出河的某一岸上的情况就可以了, 为方便起见, 我们选择传教士与野人开始所在的岸为所要表示的岸, 并称其为左岸, 另一岸称为右岸。但显然仅用描述左岸的三元组描述就足以表示出整个情况, 因此必须十分重视选择较好的问题表示法。以后的讨论还可以看到高效率的问题求解过程与控制策略有关, 合适的控制策略可缩小状态空间的搜索范围, 提高求解的效率。因而问题的初始状态是(3 3 1), 目标状态是(0 0 0)。
(1) 综合数据库: 用三元组表示, 即 (ML, CL, BL), 其中0≤ML, CL≤3, BL∈{0, 1}
此时问题述简化为 (3, 3, 1)® (0, 0, 0)
N=3的M-C问题, 状态空间的总状态数为4×4×2=32, 根据约束条件的要求, 可以看出只有20个合法状态。再进一步分析后, 又发现有4个合法状态实际上是不可能达到的。因此实际的问题空间仅由16个状态构成。下表列出分析的结果:
(ML, CL, BL) (ML, CL, BL)
(0 0 1)达不到 (0 0 0)
(0 1 1) (0 1 0)
(0 2 1) (0 2 0)
(0 3 1) (0 3 0)达不到
(1 0 1)不合法 (1 0 0)不合法
(1 1 1) (1 1 0)
(1 2 1)不合法 (1 2 0)不合法
(1 3 1)不合法 (1 3 0)不合法
(2 0 1)不合法 (2 0 0)不合法
(2 1 1)不合法 (2 1 0)不合法
(2 2 1) (2 2 0)
(2 3 1)不合法 (2 3 0)不合法
(3 0 1)达不到 (3 0 0)
(3 1 1) (3 1 0)
(3 2 1) (3 2 0)
(3 3 1) (3 3 0)达不到
(2) 规则集合: 由摆渡操作组成。该问题主要有两种操作: pmc操作(规定为从左岸划向右岸)和qmc操作(从右岸划向左岸)。每次摆渡操作, 船上人数有五种组合, 因而组成有10条规则的集合。下面定义的规则前5条为pmc操作(从左岸划向右岸), 后5条为qmc操作(从右岸划向左岸)。
if (ML, CL, BL=1) then (ML-1, CL, BL-1); (p10操作)
if (ML, CL, BL=1) then (ML, CL-1, BL-1); (p01操作)
if (ML, CL, BL=1) then (ML-1, CL-1, BL-1); (p11操作)
if (ML, CL, BL=1) then (ML-2, CL, BL-1); (p20操作)
if (ML, CL, BL=1) then (ML, CL-2, BL-1); (p02操作)
if (ML, CL, BL=0) then (ML+1, CL, BL+1); (q10操作)
if (ML, CL, BL=0) then (ML, CL+1, BL+1); (q01操作)
if (ML, CL, BL=0) then (ML+1, CL+1, BL+1); (q11操作)
if (ML, CL, BL=0) then (ML+2, CL, BL+1); (q20操作)
if (ML, CL, BL=0) then (ML, CL+2, BL+1); (q02操作)
(3) 初始和目标状态: 即(3, 3, 1)和(0, 0, 0)。和八数码游戏的问题一样, 建立了产生式系统描述之后, 就可以通过控制策略, 对状态空间进行搜索, 求得一个摆渡操作序列, 使其实现目标状态。
在讨论用产生式系统求解问题时, 有时引入状态空间图的概念很有帮助。状态空间图是一个有向图, 其节点可表示问题的各种状态(综合数据库), 节点之间的弧线代表一些操作(产生式规则), 它们可把一种状态导向另一种状态。这样建立起来的状态空间图, 描述了问题所有可能出现的状态及状态和操作之间的关系, 因而可以较直观地看出问题的解路径及其性质。实际上只有问题空间规模较小的问题才可能作出状态空间图, 例如N=3的M-C问题,的其状态空间图如下图所示, 此时采用的控制策略为顺序选取规则。由于每个摆渡操作都有对应的逆操作, 即pmc对应qmc, 所以该图也可表示成具有双向弧的形式。
从状态空间图看出解序列相当之多, 但最短解序列只有4个, 例如
(p11、q10、p02、q01、p20、q11、p20、q01、p02、q01、p02)、
(p11、q10、p02、q01、p02、q11、p20、q01、p02、q10、p11)、
(p02、q01、p02、q01、p20、q11、p20、q01、p02、q01、p02)、
(p02、q01、p02、q01、p20、q11、p20、q01、p02、q10、p11),
均由11次摆渡操作构成。若给定其中任意两个状态分别作为初始和目标状态, 就立即可找出对应的解序列来。在一般情况下, 求解过程就是对状态空间搜索出一条解路径的过程。
以上这个例子说明了建立产生式系统描述的过程, 这也就是所谓问题的表示。对问题表示的好坏, 往往对求解过程的效率有很大影响。一种较好的表示法会简化状态空间和规则集表示, 例如八数码问题中, 如用将牌移动来描述规则, 则8块将牌就有32条的规则集, 显然用空格走步来描述就简单得多。又如M-C问题中, 用3×2的矩阵给出左、右岸的情况来表示一种状态当然可以, 但显然仅用描述左岸的三元组描述就足以表示出整个情况。
如果我们用micro-PROLOG的SIMPLE语法来进行编程以实现求解乘渡的方案, 则根据你给出的询问是which(或all)还是is能给出全部摆渡方案或一个摆渡方案。下面的程序由于M-C关系的第一个语句用了回溯控制“/”, 所以实际上只能得出一个乘渡方案。
下面我们进行程序设计:
我们把满足条件的状态称为安全状态, 首先要定义出安全状态, 通过对问题的分析, 不难得出只有满足以下条件之一的状态才是安全的(以左岸为例):
1. 传教士与野人的数目相等;
2. 传教士都在左岸;
3. 传教士都不在左岸。
safe关系的三个句子分别定义了这三种不同的情况, 其中safe的第一个变元表示传教士的数目, 第二个变元表示野人的数目。
从一个状态到另一状态的转换, 通过关系Rule来完成, 它有两个句子, 分别描述了从左岸到右岸和从右岸到左岸的状态转换。Rule的两个变元都是三元组, 第一个三元组表示当前状态, 第二个三元组是得到的下一个满足条件的状态。
关系M-C通过调用Rule来求解出传教士与野人的过河方案, 它有四个变元, 第一个变元是当前状态, 调用时用初始状态代入, 第二个变元是目标状态, 第三个变元是一个中间变量, 它以表的形式记录到目前为止所达到的状态, 调用的初始值是只含初始状态一个元素的表, 第四个变元也是一个表, 求解结束时, 它以逆序形式得到解的路径。