`
ldb19890624
  • 浏览: 243677 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

算法系列之十四:狼、羊、菜和农夫过河问题

 
阅读更多

题目描述:农夫需要把狼、羊、菜和自己运到河对岸去,只有农夫能够划船,而且船比较小,除农夫之外每次只能运一种东西,还有一个棘手问题,就是如果没有农夫看着,羊会偷吃菜,狼会吃羊。请考虑一种方法,让农夫能够安全地安排这些东西和他自己过河。

这个题目考察人的快速逻辑运算和短期记忆力。分析一下,在狼-》羊-》菜这个食物链条中,“羊”处在关键位置,解决问题的指导思想就是将“羊”与“狼”和“菜”始终处于隔离状态,也就是说“羊”应该总是最后被带过河的。来看一个答案:

农夫带羊过河

农夫返回

农夫带狼过河

农夫带羊返回

农夫带菜过河

农夫返回

农夫带羊过河

<结束>

再看一个答案:

农夫带羊过河

农夫返回

农夫带菜过河

农夫带羊返回

农夫带狼过河

农夫返回

农夫带羊过河

<结束>

解决问题都是围绕着羊进行的。

上面已经提到了两个答案,那么,这个问题到底有多少中答案呢?答案还需要用计算机进行穷举。用计算机解决这个问题的关键还是状态遍历,这个和《算法系列――三个水桶均分水问题》一文中提到的状态遍历是一个道理,归根到底就是一个有限状态机。农夫、狼、羊和菜根据它们的位置关系可以有很多个状态,但总的状态数还是有限的,我们的算法就是在这些有限个状态之间遍历,直到找到一条从初始状态转换到终止状态的“路径”,并且根据题目的要求,这条“路径”上的每一个状态都应该是合法的状态。

本题无论是状态建模还是状态转换算法,都比“用三个水桶均分8升水”要简单。首先是状态,农夫、狼、羊和菜做为四个独立的Item,它们的状态都很简单,要么是过河,要么是没有过河,任意时刻每个Item的状态只有一种。如果用“HERE”表示没有过河,用“THERE”表示已经过河,用[农夫,狼,羊,菜]四元组表示某个时刻的状态,则本题的状态空间就是以[HEREHEREHEREHERE]为根的一棵状态树,当这个状态树的某个叶子节点是状态[THERETHERETHERETHERE],则表示从根到这个叶子节点之间的状态序列就是本问题的一个解。

本题的状态转换算法依然是对状态空间中所有状态进行深度优先搜索,因为狼、羊和菜不会划船,所以状态转换算法也很简单,不需要象“用三个水桶均分8升水”问题那样要用排列组合的方式确定转换方法(倒水动作),本题一共只有8种固定的状态转换运算(过河动作),分别是:

农夫单独过河;

农夫带狼过河;

农夫带羊过河;

农夫带菜过河;

农夫单独返回;

农夫带狼返回;

农夫带羊返回;

农夫带菜返回;

本题的广度搜索边界就是这8个动作,依次对这8个动作进行遍历最多可以转换为8个新状态,每个新状态又最多可以转化为8个新新状态,就形成了每个状态节点有8个(最多8个)子节点的状态树(八叉树)。本题算法的核心就是对这个状态树进行深度优先遍历,当某个状态满足结束状态时就输出一组结果。

需要注意的是,并不是每个动作都可以得到一个新状态,比如“农夫带狼过河”这个动作,对于那些狼已经在河对岸的状态就是无效的,无法得到新状态,因此这个八叉树并不是满树。除此之外,题目要求的合法性判断也可以砍掉很多无效的状态。最后一点需要注意的是,即使是有效的状态,也会有重复,在一次深度遍历的过程中如果出现重复的状态可能会导致无穷死循环,因此要对重复出现的状态进行“剪枝”。

程序实现首先要描述状态模型,本算法的状态定义为:

33struct ItemState

34{

35 ......

43 State farmer,wolf,sheep,vegetable;

44 Action curAction;

35 ......

45};

算法在穷举的过程中需要保存当前搜索路径上的所有合法状态,考虑到是深度优先算法,用Stack是最佳选择,但是Stack没有提供线性遍历的接口,在输出结果和判断是否有重复状态时都需要线性遍历保存的状态路径,所以本算法不用Stack,而是用Deque(双端队列)。

整个算法的核心就是ProcessState()函数,ProcessState()函数通过对自身的递归调用实现对状态树的遍历,代码如下:

291void ProcessState(deque<ItemState>& states)

292{

293 ItemState current = states.back(); /*每次都从当前状态开始*/

294 if(current.IsFinalState())

295 {

296 PrintResult(states);

297 return;

298 }

299

300 ItemState next;

301 for(int i = 0; i < action_count; ++i)

302 {

303 if(actMap[i].processFunc(current, next))

304 {

305 if(IsCurrentStateValid(next) && !IsProcessedState(states, next))

306 {

307 states.push_back(next);

308 ProcessState(states);

309 states.pop_back();

310 }

311 }

312 }

313}

参数states是当前搜索的状态路径上的所有状态列表,所以ProcessState()函数首先判断这个状态列表的最后一个状态是不是最终状态,如果是则说明这个搜索路径可以得到一个解,于是调用PrintResult()函数打印结果,随后的return表示终止设个搜索路径上的搜索。如果还没有达到最终状态,则依次从8个固定的过河动作得到新的状态,并从新的状态继续搜索。为了避免长长的switch…case语句,程序算法使用了表驱动的方法,将8个固定过河动作的处理函数放在一张映射表中,用简单的查表代替switch…case语句。映射表内容如下:

279ActionProcess actMap[action_count] =

280{

281 { FARMER_GO, ProcessFarmerGo },

282 { FARMER_GO_TAKE_WOLF, ProcessFarmerGoTakeWolf },

283 { FARMER_GO_TAKE_SHEEP, ProcessFarmerGoTakeSheep },

284 { FARMER_GO_TAKE_VEGETABLE, ProcessFarmerGoTakeVegetable },

285 { FARMER_BACK, ProcessFarmerBack },

286 { FARMER_BACK_TAKE_WOLF, ProcessFarmerBackTakeWolf },

287 { FARMER_BACK_TAKE_SHEEP, ProcessFarmerBackTakeSheep },

288 { FARMER_BACK_TAKE_VEGETABLE, ProcessFarmerBackTakeVegetable }

289};

表中的处理函数非常简单,就是根据当前状态以及过河动作,得到一个新状态,如果过河动作与当前状态矛盾,则返回失败,以FARMER_GO_TAKE_WOLF动作对应的处理函数ProcessFarmerGoTakeWolf()为例,看看ProcessFarmerGoTakeWolf()函数的代码:

182bool ProcessFarmerGoTakeWolf(const ItemState& current, ItemState& next)

183{

184 if((current.farmer != HERE) || (current.wolf != HERE))

185 return false;

186

187 next = current;

188

189 next.farmer = THERE;

190 next.wolf = THERE;

191 next.curAction = FARMER_GO_TAKE_WOLF;

192

193 return true;

194}

当过河动作对应的处理函数返回成功,表示可以得到一个不矛盾的新状态时,就要对新状态进行合法性检查,首先是检查是否满足题目要求,比如狼和羊不能独处以及羊和菜不能独处,等等,这个检查在IsCurrentStateValid()函数中完成。接着是检查新状态是否和状态路径上已经处理过的状态有重复,这个检查由IsProcessedState()函数完成,IsProcessedState()函数的实现也很简单,就是遍历states,与新状态比较是否有相同状态,代码如下:

131bool IsProcessedState(deque<ItemState>& states, ItemState& newState)

132{

133 deque<ItemState>::iterator it = find_if( states.begin(), states.end(),

134 bind2nd(ptr_fun(IsSameItemState), newState) );

135

136 return (it != states.end());

137}

运行程序,最终得到的结果是:

Find Result 1:

Unknown action, item states is : 0 0 0 0

Farmer take sheep go over river, item states is : 1 0 1 0

Farmer go back, item states is : 0 0 1 0

Farmer take wolf go over river, item states is : 1 1 1 0

Farmer take sheep go back, item states is : 0 1 0 0

Farmer take vegetable go over river, item states is : 1 1 0 1

Farmer go back, item states is : 0 1 0 1

Farmer take sheep go over river, item states is : 1 1 1 1

Find Result 2:

Unknown action, item states is : 0 0 0 0

Farmer take sheep go over river, item states is : 1 0 1 0

Farmer go back, item states is : 0 0 1 0

Farmer take vegetable go over river, item states is : 1 0 1 1

Farmer take sheep go back, item states is : 0 0 0 1

Farmer take wolf go over river, item states is : 1 1 0 1

Farmer go back, item states is : 0 1 0 1

Farmer take sheep go over river, item states is : 1 1 1 1

看来确实是只有两种结果。

分享到:
评论

相关推荐

    人工智能 狼 羊 白菜 农夫过河

    总的来说,"农夫过河"问题展示了逻辑推理和算法设计在解决实际问题中的应用。它不仅锻炼了我们的思维能力,也为我们理解计算机如何处理类似问题提供了基础。在实际的计算机科学中,这类问题常被用来作为搜索算法、...

    农夫过河问题的算法与实现.doc

    农夫过河问题是指农夫带一只狼、一只羊和一棵白菜在河南岸,需要安全运到北岸。这类问题的实质是系统的状态问题,要寻求的是从初始状态经一系列的安全状态到达系统的终止状态的一条路径。 知识点一:状态空间搜索 ...

    农夫,狼,羊, 菜,过河经典问题

    《农夫,狼,羊,菜,过河问题与有限状态机解法》 过河问题,这是一个经典的逻辑思维题目,涉及到的是如何在一个有限的环境中,通过合理的操作序列达成特定的目标。在这个问题中,农夫需要将狼、羊和菜全部安全地从...

    狼,羊,白菜过河的小程序

    【狼、羊、白菜过河小程序】是一种经典的逻辑问题,源于中国古代的智力谜题,也被广泛应用于编程教学和算法设计中。在这个问题中,有一条狭窄的河流,一个农夫需要将一只狼、一只羊和一颗白菜从河的一边安全地送到另...

    基于C++的农夫过河问题算法设计与实现方法

    在本文中,我们首先介绍了农夫过河问题的描述,即一个农夫带着一只狼、一只羊和一棵白菜,身处河的南岸。他要把这些东西全部运到北岸。他面前只有一条小船,船只能容下他和一件物品,另外只有农夫才能撑船。如果农夫...

    狼羊菜过河问题源程序

    cout有一个农夫带一只羊、一筐菜和一只狼过河. "; cout如果没有农夫看管,则狼要吃羊,羊要吃菜."; cout但是船很小,只够农夫带一样东西过河。"; cout问农夫该如何解此难题?";

    农夫过河的代码

    农夫过河的完整代码(农夫、羊、狼和蔬菜安全过河)

    农夫过河(狼,羊,菜)C++实现

    一个农夫带着一只狼,一只羊和一筐菜,欲从河的左岸坐船到右岸,由于船太小,农夫每次只能带一样东西过河,并且没有农夫看管的话,狼会吃掉羊,羊会吃菜。设计一个方案,使农夫可以无损失的过河。 代码请用VS2010...

    农夫过河的问题

    "农夫过河"是一个经典的逻辑谜题,它涉及到如何有效地安排运输物品和人物过河的问题。在这个问题中,通常设定有农夫、他的儿子、他的女儿、一只狼、一只羊和一颗白菜,只有一艘小船,且每次只能载两个人或物。问题的...

    农夫过河问题【代码+流程图+可执行文件】

    总之,农夫过河问题的解决方法揭示了图论和搜索算法在解决实际问题中的应用,同时也锻炼了我们的逻辑思维和编程能力。通过理解和实现这个经典问题的解决方案,我们可以更好地掌握这些基础概念,并将其应用于更广泛的...

    经典的狼羊菜过河问题

    "狼羊菜过河"问题就是一个这样的例子,它源于古老的数学谜题,现在常被用作编程挑战和教育素材,特别是对于学习算法和逻辑思维的人来说。这个问题的设定是:一个农夫需要将一只狼、一只羊和一棵白菜安全地运过一条河...

    农夫过河问题(C语言)设计

    经典的农夫过河问题。 用1代表狼,2代表羊,3代表白菜。则在河的某一岸边,物体的分布有8种情况: 当两物体在一起并且它们的代码之和为3或5时,将导致相克的情况出现。 设计c语言算法实现过河,并将结果打印

    Java简单实现农夫过河问题示例

    3. 农夫过河:如果当前河岸安全,农夫可以带着一个动物过河。 4. 更新河岸状态:更新河岸的状态,记录当前河岸上剩余的动物。 5. 重复步骤2-4:直到所有动物都过河为止。 四、农夫过河问题的应用 农夫过河问题不...

    农夫过河问题(邻接矩阵、深度遍历算法)解决

    有一农夫带着一条狼,一只羊,一筐菜想过河,农夫每次只能带一件东西,如果没有农夫看管,狼会吃羊,羊吃草,如何设计使得每个东西都能安全过河

    农夫过河深度遍及c++

    因为狼、羊和菜不会划船,所以状态转换算法也很简单,不需要象“用三个水桶均分8升水”问题那样要用排列组合的方式确定转换方法(倒水动作),本题一共只有8种固定的状态转换运算(过河动作),分别是: 农夫单独...

    数据结构农夫过河问题

    问题的核心在于如何安全地通过一系列操作将狼、羊和白菜都运送到河的对岸,同时保证在农夫不在场时,任何可能导致危险的情况都不会发生。 **问题分析:** 1. **安全条件**:农夫不在场时,不能让狼和羊单独在一起,...

    C++基于人工智能搜索策略解决农夫过河问题示例

    本文实例讲述了C++基于人工智能搜索策略解决农夫过河问题。分享给大家供大家参考,具体如下: 问题描述 一农夫带着一头狼,一只羊和一个白菜过河,小船只能一次装载农夫和一样货物,狼会吃羊,羊会吃白菜,只有农夫...

    农夫过河C++

    农夫需要把狼、羊、菜和自己运到河对岸去,只有农夫能够划船,而且船比较小,除农夫之外每次只能运一种东西,还有一个棘手问题,就是如果没有农夫看着,羊会偷吃菜,狼会吃羊。请考虑一种方法,让农夫能够安全地安排...

    c语言农夫过河思维游戏程序及源代码

    此项目将农夫过河问题转化为一个游戏,旨在锻炼玩家的逻辑思维和解决问题的能力。通过编程实现,玩家可以尝试各种过河策略,程序会判断是否符合规则并给出反馈。 【源代码】 提供的源代码是实现这个游戏的关键,它...

    数据结构农夫过河问题课设

    农夫过河问题是一个经典的问题,源自中国古代的智力游戏,也被称为“狼、羊、白菜”问题。问题描述如下:农夫需要将自己、一只狼、一只羊和一棵白菜运过一条河,但他的小船只能承载他自己和一件物品。如果他离开狼和...

Global site tag (gtag.js) - Google Analytics