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

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

农夫带羊过河

农夫返回

农夫带狼过河

农夫带羊返回

农夫带菜过河

农夫返回

农夫带羊过河

再看一个答案:

农夫带羊过河

农夫返回

农夫带菜过河

农夫带羊返回

农夫带狼过河

农夫返回

农夫带羊过河

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

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

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

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

农夫单独过河;

农夫带狼过河;

农夫带羊过河;

农夫带菜过河;

农夫单独返回;

农夫带狼返回;

农夫带羊返回;

农夫带菜返回;

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

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

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

33 struct ItemState

34 {

35   ......

43   State  farmer,wolf,sheep,vegetable;

44   Action curAction;

35   ......

45 };

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

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

291 void ProcessState(deque& 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语句。映射表内容如下:

279 ActionProcess 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()函数的代码:

182 bool 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,与新状态比较是否有相同状态,代码如下:

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

132 {

133     deque::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

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

狼羊菜过河问题c语言算法,算法系列之十四:狼、羊、菜和农夫过河问题相关推荐

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

    算法系列之十四:狼.羊.菜和农夫过河问题 题目描述:农夫需要把狼.羊.菜和自己运到河对岸去,只有农夫能够划船,而且船比较小,除农夫之外每次只能运一种东西,还有一个棘手问题,就是如果没有农夫看着,羊会偷 ...

  2. 经典算法研究系列:十、从头到尾彻底理解傅里叶变换算法、上

     经典算法研究系列:十.从头到尾彻底理解傅里叶变换算法.上 作者:July.dznlong   二零一一年二月二十日 推荐阅读:The Scientist and Engineer's Guide t ...

  3. c语言int型等长输出,c语言先程序设计15第十四讲第六章下.ppt

    c语言先程序设计15第十四讲第六章下 高级语言程序设计 主讲教师:贾彩燕 计算机与信息技术学院 计算机科学与技术系 cyjia@ 第六章 数组 主要内容 数组的概念.定义和使用 数组程序实例 数组作为 ...

  4. 经典C语言程序100例之十四

    经典C语言程序100例之十四 如题 话不多说了,直接上代码 如题 程序14] 题目:将一个正整数分解质因数.例如:输入90,打印出90=233*5. 程序分析:对n进行分解质因数,应先找到一个最小的质 ...

  5. 经典算法研究系列:十、从头到尾彻底理解傅里叶变换算法、下

    经典算法研究系列:十.从头到尾彻底理解傅里叶变换算法.下 作者:July.dznlong   二零一一年二月二十二日 推荐阅读:The Scientist and Engineer's Guide t ...

  6. c语言三级上机题库,2006年9月全国等级考试三级c语言上机题库(三十四)

    ★☆题目34(无忧id 73,102 素数题) 无忧id 102 题提供了求素数isPrime()函数 程序prog1.c的功能是:选出100以上1000之内所有个位数字与十位数字之和被10除所得余数 ...

  7. SVM 支持向量机算法(Support Vector Machine )【Python机器学习系列(十四)】

    SVM 支持向量机算法(Support Vector Machine )[Python机器学习系列(十四)] 文章目录 1.SVM简介 2. SVM 逻辑推导 2.1 Part1 化简限制条件 2.2 ...

  8. leetcode算法专题训练:十四.位操作专题

    文章目录 十四.位操作专题 50.Pow(x,n) 69.x的平方根 136.只出现一次的数字 137.只出现一次的数字2 260.只出现一次的数字3 89.格雷编码 剑指 Offer 64. 求1+ ...

  9. 多人过河问题C语言贪心算法,贪心算法----过河问题

    问题: 在漆黑的夜里,N位旅行者来到了一座狭窄而且没有护栏的桥边.如果不借助手电筒的话,大家是无论如何也不敢过桥去的.不幸的是,N个人一共只带了一只手电筒,而桥窄得只够让两个人同时过.如果各自单独过桥 ...

最新文章

  1. ASP.NET2.0 GridView小技巧汇粹
  2. msfvenom java_Msfvenom命令总结大全
  3. python调用adb传输电脑文件到手机_使用adb在电脑和手机间传文件
  4. [转]一文解释PyTorch求导相关 (backward, autograd.grad)
  5. tlab java_浅析java中的TLAB
  6. 2010年最具潜力微博网站排行榜(转)
  7. 华为云GaussDB专家走进课堂,跟莘莘学子聊聊数据库
  8. Win7小工具“概念时钟”,v2.1.8.6
  9. 转: MATLAB: cat函数使用
  10. 关于TCP或FTP异常断开的处理方法总结
  11. Cocos2d-x Tiled地图编辑器(一)基本使用
  12. iOS 去掉UISearchBar输入框上面的黑线
  13. 深入浅出Linux设备驱动编程--引言
  14. python 数据分析实践--(1)收入预测分析
  15. 小米5之Root攻略
  16. 如何找回iPhone的访问限制密码
  17. ubuntu命令行更新vscode
  18. C++中string.find()函数与string::npos
  19. 算法时间复杂度以及代码实现
  20. Hbase的Regina分区

热门文章

  1. python和revit_Python 與 Revit
  2. 人工智能将为中小企业发展带来什么?
  3. arduino驱动lcd1602
  4. java final成员变量吗_阳光沙滩博客-为什么Java匿名内部内使用局部变量需要加final?而访问成员变量却不用加final呢?...
  5. 全功能Python测试框架:pytest
  6. 和图片有关的几个旋转属性
  7. android获取当前连接的蓝牙名称,获取当前连接的蓝牙设备的名称
  8. eclipse中没有js代码提示的解决方案
  9. 与门非门在电子计算机中的应用,【E电路】数字电路基础:与门电路
  10. vue中拿到接口,并获取数据,渲染到页面