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

算法设计思路

这是一个很简单的问题,在狼、羊和菜这个食物链上,关键是中间的羊,因为狼不吃菜,所以要安全过河,农夫的第一件事就是带羊走,拆开这个食物链。但是计算机无法理解这个关键的羊,所以我们采用穷举法来解决这个问题,同时借助于穷举搜索找出所有过河的方法。

定义问题的状态

在确定以何种方法对解空间进行穷举搜索之前,首先要定义问题的解。虽然这个题目的要求是给出农夫带着他的小伙伴过河的动作,**但是单纯考虑对动作的穷举是没有意义的,因为问题最后的解决状态是农夫、狼、羊和菜过到河对岸,能最终产生这种状态的动作才有意义,为了判断动作的有效性,需要定义一个合适的状态来描述这个游戏在某个时刻的局面。**考虑一下这个题目涉及的所有元素:农夫、狼、羊、菜、船和河,河是固定的,没有状态变化,因为只有农夫可以划船,所以船可以看作和农夫是一体的,简化后其实有 4 个元素需要考虑,分别是农夫、狼、羊和菜。如下图所示的一种过河的过程,状态的定义只要能表达农夫、狼、羊和菜的位置关系即可。

从上图可以看出来,状态(a)(h)变化的其实就是这 4 个元素的位置,每个元素的位置只有两个状态,即在河的左岸和在河的右岸;问题的初始状态是农夫、狼、羊和菜都在河的左岸,得到解的终止状态是农夫、狼、羊和菜都在河的右岸。我们用一个四元组来表示四个元素的位置状态,那么初始状态为 [Left,Left,Left,Left],最终的结果状态为 [Right,Right,Right,Right]

状态树和解空间

对所有状态穷举搜索的结果也是一棵状态树,根节点是初始状态,叶子节点就是解决问题的状态。**这个题目由于限制条件比较特殊,只有农夫可以划船,一次只能带一个小伙伴,同时狼、羊和菜又不能愉快地在一起玩耍,所以状态树上很多状态都是非法状态,客观上起到了很好的剪枝效果。**如下图所示的状态树中,蓝色的状态表示此状态已经和之前的状态重复,红色的状态表示这是一个非法状态,不是出现狼吃羊的情况,就是出现羊吃菜的情况。绿色状态是搜索到了结果状态,这是状态树的叶子节点

根据题目的意图,最终的结果是要输出这条转换路径的过河过程,实际上就是与状态转换路径相对应的动作路径,或动作列表。当定义了动作的数学模型后,就可以根据状态图中状态转换路径找到对应的动作列表,依次输出这个路径上每个状态对应的动作就可以得到一个完整的过河过程。

状态的数据模型

根据之前对状态的定义,状态的数据模型就是农夫、狼、羊和菜的位置,位置定义 LOCATION 就是两个状态,LOCATION::LEFT 表示对应的元素在河的左岸,LOCATION::RIGHT 表示对应元素在河的右岸(就是过河状态)。

struct ItemState
{
......LOCATION farmer,wolf,sheep,vegetable; //四个元素的位置ACTION curAction; //此状态对应的动作
};

过河动作的数据模型

两个静态的位置状态是通过过河动作建立关联的,因为只有农夫会划船。农夫可以自己过河,也可以带一个物品过河,当然,从河对岸返回时也是一样的情况。由此可见,本问题的过河动作其实只有 8 个固定动作,分别是:

  • (1)农夫自己过河
  • (2)农夫带狼过河
  • (3)农夫带羊过河
  • (4)农夫带菜过河
  • (5)农夫自己返回
  • (6)农夫带狼返回
  • (7)农夫带羊返回
  • (8)农夫带菜返回

但是需要注意的是,并不是所有的动作都适用于对应的状态,比如对于农夫在河的左岸的状态,动作(5)~(8)代表的返回动作就都不适用。同样,对于一个菜已经在河对岸的状态,动作(4)就不适用。在搜索过程中对过河动作遍历的时候,要根据这些情况合理地剪掉一些分支。根据以上描述,过河动作 ACTION 定义如下:

enum class ACTION
{GO_SELF,GO_WITH_WOLF,GO_WITH_SHEEP,GO_WITH_VEGETABLE,BACK_SELF,BACK_WITH_WOLF,BACK_WITH_SHEEP,BACK_WITH_VEGETABLE,
};

设计搜索算法

我们讨论的状态都是静止的,如果不是有过河动作作用到状态上,状态是不会发生变化的。对状态树的搜索,其实就是把 8 个过河动作依次与状态结合,演变为新的状态的过程。本题的搜索算法采用深度优先遍历,从初始状态节点开始,一直搜索到合法状态为止,在这个过程中,需要记录已经搜索过的状态,避免出现重复状态导致算法进入死循环。

状态树的遍历

不同的过河动作也会对状态产生不同的变化。比如ACTION::GO_WITH_VEGETABLE动作,将使得 ItemState中 farmer 和 vegetable 的位置值从 LOCATION::LEFT 转变为 LOCATION::RIGHT。由于 8 个过河动作对状态的影响无法用一个一致的代码逻辑进行处理,所以我们为每个过河动作定义一个代码处理逻辑,8 个动作对应 8 个代码处理逻辑:

struct ActionProcess
{ACTION  act_name;std::function<bool(const ItemState& current, ItemState& next)>  TakeAction;
};ActionProcess actMap[] =
{{ ACTION::GO_SELF,                ProcessFarmerGo                },{ ACTION::GO_WITH_WOLF,           ProcessFarmerGoTakeWolf        },{ ACTION::GO_WITH_SHEEP,          ProcessFarmerGoTakeSheep       },{ ACTION::GO_WITH_VEGETABLE,      ProcessFarmerGoTakeVegetable   },{ ACTION::BACK_SELF,              ProcessFarmerBack              },{ ACTION::BACK_WITH_WOLF,         ProcessFarmerBackTakeWolf      },{ ACTION::BACK_WITH_SHEEP,        ProcessFarmerBackTakeSheep     },{ ACTION::BACK_WITH_VEGETABLE,    ProcessFarmerBackTakeVegetable }
};

每个处理逻辑需要做三件事情

  • 首先要判断当前状态是否适用于对应的动作
  • 接着根据对状态进行相应的改变并将对应的动作记录到新状态中
  • 最后判断转化后的状态是不是一个合法的状态。

仍然以ACTION::GO_WITH_VEGETABLE动作为例,其处理逻辑ProcessFarmerGoTakeVegetable()的实现如下:

bool ProcessFarmerGoTakeVegetable(const ItemState& current, ItemState& next)
{if((current.farmer != LOCATION::LEFT) || (current.vegetable != LOCATION::LEFT))return false;next = current;next.farmer    = LOCATION::RIGHT;next.vegetable = LOCATION::RIGHT;next.curAction = ACTION::GO_WITH_VEGETABLE;return IsCurrentStateValid(next);
}

对 8 个动作循环一遍,即可完成对一个状态的遍历,并根据情况生成新的状态,所以,遍历的动作就是对 actMap 做一个循环,依次调用每个动作对应的 TakeAction 逻辑。遍历代码的主体大致是这样的:

ItemState next;for (auto& act : actMap){if(act.TakeAction(current, next)){......}}

需要剪枝处理的情况有两种,一种是非法状态,另一种是重复的状态。对非法状态的过滤,体现在过河动作对应的处理逻辑中,ProcessFarmerGoTakeVegetable() 函数中最后调用 IsCurrentStateValid()判断这个状态是不是合法状态。对于产生非法状态的情况,ProcessFarmerGoTakeVegetable()函数返回false,遍历循环就跳过这个动作,继续遍历下一个动作。

对重复状态的过滤,是在 TakeAction 逻辑返回true 的情况下才进行的,如下代码所示,两个 if 判断就是对上述两种情况的剪枝处理

if(act.TakeAction(current, next))
{if(!IsProcessedState(states, next)){......}
}

代码实现

搜索算法代码

SearchState() 函数实现状态树的搜索遍历,由两部分内容组成:第一部分的 IsFinalState() 函数判断当前状态序列中最后一个状态是不是最终结果状态,如果是就输出一组状态序列(以及对应的过河动作);如果当前状态序列中最后一个状态不是结果状态,则转入第二部分开始搜索新的状态。

void SearchStates(deque<ItemState>& states)
{ItemState current = states.back(); /*每次都从当前状态开始*/if(current.IsFinalState()){PrintResult(states);return;}ItemState next;for (auto& act : actMap){if(act.TakeAction(current, next)){if(!IsProcessedState(states, next)){states.push_back(next);SearchStates(states);states.pop_back();}}}
}

判断非法动作和重复状态

  • 如果狼和羊位置相同,并且农夫的位置与它们不同,则是非法状态;
  • 如果羊和菜位置相同,并且农夫的位置与它们不同,则是非法状态;
  • 其他情况均为合法状态。
bool IsCurrentStateValid(const ItemState& current)
{if ((current.wolf == current.sheep) && (current.sheep != current.farmer)){return false;}if ((current.vegetable == current.sheep) && (current.sheep != current.farmer)){return false;}return true;
}

重复状态的判断更简单,就是对状态队列进行一次查找操作:

bool IsProcessedState(deque<ItemState>& states, const ItemState& newState)
{auto it = find_if( states.begin(), states.end(),[&newState](ItemState& item) { return item.IsSameState(newState); });return (it != states.end());
}

完整代码

#include <map>
#include <string>
#include <deque>
#include <algorithm>
#include <functional>
#include <iostream>
#include <vector>
#include <cassert>enum class LOCATION
{LEFT,RIGHT
};enum class ACTION
{GO_SELF,GO_WITH_WOLF,GO_WITH_SHEEP,GO_WITH_VEGETABLE,BACK_SELF,BACK_WITH_WOLF,BACK_WITH_SHEEP,BACK_WITH_VEGETABLE,INVALID,
};struct ItemState
{ItemState();bool IsSameState(const ItemState& state) const;void PrintStates();bool IsFinalState();LOCATION farmer,wolf,sheep,vegetable;ACTION curAction;
};struct ActionProcess
{ACTION  act_name;std::function<bool(const ItemState& current, ItemState& next)>  TakeAction;
};std::map<LOCATION, std::string> locationMap = {{ LOCATION::LEFT ,         "Left" },{ LOCATION::RIGHT ,        "Right" }
};std::map<ACTION, std::string> actMsgMap = {{ ACTION::GO_SELF ,             "Farmer go over the river" },{ ACTION::GO_WITH_WOLF ,        "Farmer take wolf go over the river" },{ ACTION::GO_WITH_SHEEP ,       "Farmer take sheep go over the river" },{ ACTION::GO_WITH_VEGETABLE ,   "Farmer take vegetable go over the river" },{ ACTION::BACK_SELF ,                "Farmer go back" },{ ACTION::BACK_WITH_WOLF ,      "Farmer take wolf go back" },{ ACTION::BACK_WITH_SHEEP ,     "Farmer take sheep go back" },{ ACTION::BACK_WITH_VEGETABLE , "Farmer take vegetable go back" },{ ACTION::INVALID , "" }
};ItemState::ItemState()
{farmer = LOCATION::LEFT;wolf = LOCATION::LEFT;sheep = LOCATION::LEFT;vegetable = LOCATION::LEFT;curAction = ACTION::INVALID;
}bool ItemState::IsSameState(const ItemState& state) const
{return ( (farmer == state.farmer) && (wolf == state.wolf) && (sheep == state.sheep) && (vegetable == state.vegetable) );
}void ItemState::PrintStates()
{std::cout << actMsgMap[curAction] << "  ";std::cout << ": " << "framer " <<locationMap[farmer] << ", wolf " << locationMap[wolf] << ", sheep " << locationMap[sheep] << ", vegetable " << locationMap[vegetable] << std::endl;
}bool ItemState::IsFinalState()
{return ( (farmer == LOCATION::RIGHT) && (wolf == LOCATION::RIGHT) && (sheep == LOCATION::RIGHT) && (vegetable == LOCATION::RIGHT) );
}bool IsProcessedState(deque<ItemState>& states, const ItemState& newState)
{auto it = find_if( states.begin(), states.end(),[&newState](ItemState& item) { return item.IsSameState(newState); });return (it != states.end());
}void PrintResult(deque<ItemState>& states)
{cout << "Find Result : " << endl;for (auto& x : states){x.PrintStates();}cout << endl << endl;
}bool IsCurrentStateValid(const ItemState& current)
{if ((current.wolf == current.sheep) && (current.sheep != current.farmer)){return false;}if ((current.vegetable == current.sheep) && (current.sheep != current.farmer)){return false;}return true;
}bool ProcessFarmerGo(const ItemState& current, ItemState& next)
{if(current.farmer != LOCATION::LEFT)return false;next = current;next.farmer    = LOCATION::RIGHT;next.curAction = ACTION::GO_SELF;return IsCurrentStateValid(next);
}bool ProcessFarmerGoTakeWolf(const ItemState& current, ItemState& next)
{if((current.farmer != LOCATION::LEFT) || (current.wolf != LOCATION::LEFT))return false;next = current;next.farmer    = LOCATION::RIGHT;next.wolf      = LOCATION::RIGHT;next.curAction = ACTION::GO_WITH_WOLF;return IsCurrentStateValid(next);
}bool ProcessFarmerGoTakeSheep(const ItemState& current, ItemState& next)
{if((current.farmer != LOCATION::LEFT) || (current.sheep != LOCATION::LEFT))return false;next = current;next.farmer    = LOCATION::RIGHT;next.sheep     = LOCATION::RIGHT;next.curAction = ACTION::GO_WITH_SHEEP;return IsCurrentStateValid(next);
}bool ProcessFarmerGoTakeVegetable(const ItemState& current, ItemState& next)
{if((current.farmer != LOCATION::LEFT) || (current.vegetable != LOCATION::LEFT))return false;next = current;next.farmer    = LOCATION::RIGHT;next.vegetable = LOCATION::RIGHT;next.curAction = ACTION::GO_WITH_VEGETABLE;return IsCurrentStateValid(next);
}bool ProcessFarmerBack(const ItemState& current, ItemState& next)
{if(current.farmer != LOCATION::RIGHT)return false;next = current;next.farmer    = LOCATION::LEFT;next.curAction = ACTION::BACK_SELF;return IsCurrentStateValid(next);
}bool ProcessFarmerBackTakeWolf(const ItemState& current, ItemState& next)
{if((current.farmer != LOCATION::RIGHT) || (current.wolf != LOCATION::RIGHT))return false;next = current;next.farmer    = LOCATION::LEFT;next.wolf      = LOCATION::LEFT;next.curAction = ACTION::BACK_WITH_WOLF;return IsCurrentStateValid(next);
}bool ProcessFarmerBackTakeSheep(const ItemState& current, ItemState& next)
{if((current.farmer != LOCATION::RIGHT) || (current.sheep != LOCATION::RIGHT))return false;next = current;next.farmer    = LOCATION::LEFT;next.sheep     = LOCATION::LEFT;next.curAction = ACTION::BACK_WITH_SHEEP;return IsCurrentStateValid(next);
}bool ProcessFarmerBackTakeVegetable(const ItemState& current, ItemState& next)
{if((current.farmer != LOCATION::RIGHT) || (current.vegetable != LOCATION::RIGHT))return false;next = current;next.farmer    = LOCATION::LEFT;next.vegetable = LOCATION::LEFT;next.curAction = ACTION::BACK_WITH_VEGETABLE;return IsCurrentStateValid(next);
}ActionProcess actMap[] =
{{ ACTION::GO_SELF,                ProcessFarmerGo                },{ ACTION::GO_WITH_WOLF,           ProcessFarmerGoTakeWolf        },{ ACTION::GO_WITH_SHEEP,          ProcessFarmerGoTakeSheep       },{ ACTION::GO_WITH_VEGETABLE,      ProcessFarmerGoTakeVegetable   },{ ACTION::BACK_SELF,              ProcessFarmerBack              },{ ACTION::BACK_WITH_WOLF,         ProcessFarmerBackTakeWolf      },{ ACTION::BACK_WITH_SHEEP,        ProcessFarmerBackTakeSheep     },{ ACTION::BACK_WITH_VEGETABLE,    ProcessFarmerBackTakeVegetable }
};void SearchStates(deque<ItemState>& states)
{ItemState current = states.back(); /*每次都从当前状态开始*/if(current.IsFinalState()){PrintResult(states);return;}ItemState next;for (auto& act : actMap){if(act.TakeAction(current, next)){if(!IsProcessedState(states, next)){states.push_back(next);SearchStates(states);states.pop_back();}}}
}int main(int argc, char* argv[])
{deque<ItemState> states;//ItemState init = { LOCATION::LEFT, LOCATION::LEFT, LOCATION::LEFT, LOCATION::LEFT };ItemState init;states.push_back(init);SearchStates(states);assert(states.size() == 1); /*穷举结束后states应该还是只有一个初始状态*/return 0;
}

狼、羊、菜和农夫过河问题[超详细解析,CPP实现]相关推荐

  1. java狼羊草过河_狼羊菜过河问题深入学习分析——Java语言描述版

    前言 这个问题的抛出,是几个星期之前的算法课程.老师分析了半天,最后的结论是:其实就是图的遍历.那时候挺懵逼的,不管是对于图,还是遍历,或者是数据结构,心里面都没有一个十足的概念,所以搁置了这么久的问 ...

  2. 狼、羊、菜、农夫过河问题 穷举 Python实现

    仍然是王晓华老师的算法课程,穷举问题中的狼.羊.菜和农夫过河问题,该问题与"三个水桶倒水"问题相似,需穷举状态,同时仍需判断动作的有效性. 状态初始值是 [left,left,le ...

  3. 农夫过河狼羊白菜Java开放封闭_农夫过河——狼羊菜问题

    话说一位农夫带着一只狼.一只羊和一个卷心菜过河,无奈船小,农夫每次只能运送一样东西,考虑到狼吃羊.羊吃菜,因此运送的顺序至关重要. 在现实世界里解决这个问题并不困难,相信很多人都已经有了答案,但是如何 ...

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

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

  5. 【数据结构与算法】狼、羊、菜和农夫过河:使用图的广度优先遍历实现

    [数据结构与算法]狼.羊.菜和农夫过河:使用图的广度优先遍历实现 Java 农夫需要把狼.羊.菜和自己运到河对岸去,只有农夫能够划船,而且船比较小.除农夫之外每次只能运一种东西.还有一个棘手问题,就是 ...

  6. 农夫过河狼羊白菜Java开放封闭_狼、羊、菜和农夫过河问题编程实现

    用二进制数 记录状态 1 1 1 1 1 A岸 狼 菜 羊 农夫 最高位表示 A/B岸边,后面分别表示 狼菜羊农夫,0 说明这个岸边没有,1表示这个岸边有 根据题目条件 还要去掉 狼羊 单独 羊菜 单 ...

  7. 狼羊菜过河问题深入学习分析——Java语言描述版

    前言 这个问题的抛出,是几个星期之前的算法课程.老师分析了半天,最后的结论是:其实就是图的遍历.那时候挺懵逼的,不管是对于图,还是遍历,或者是数据结构,心里面都没有一个十足的概念,所以搁置了这么久的问 ...

  8. 狼羊菜过河(C实现)

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

  9. *python解决狼羊菜过河问题

    python解决狼羊菜过河问题 A岸有菜,羊,狼,农夫农夫必须将他们都送到B岸每次只能送一个,在保证他们不会被吃的前提下,完成任务,并得出步骤. 代码: A=[["狼",1],[& ...

最新文章

  1. visual studio 2015开发nodejs教程1搭建环境
  2. Python中如何查看模块的源码内容
  3. server2008中如何关闭internet explorer增强的安全配置
  4. OpenCV捕获格雷码模式
  5. 闲来无事,拆个示波器玩玩。
  6. MongoDB简介、在node中使用MongoDB
  7. zoj 1406 Jungle Roads
  8. 结对编程后传之做汉堡
  9. Chrome浏览器长截图
  10. MATLAB实现控制鼠标移动和点击
  11. AI 赋能安全技术总结与展望
  12. 风控模型基本概念和方法
  13. 网站编辑与传统媒体编辑的区别及特点
  14. 百度收录静态html吗,百度收录越多,网站排名就越高吗?
  15. jdbc操作数据库实现查询产品、增加产品库存量例子
  16. 学习篇--FPGA学习网站
  17. 2022杭电多校4 G - Climb Stairs
  18. 关于电脑磁盘满了爆红解决方法之一
  19. 【作业】非结构化数据相关知识整理
  20. Linux命令:readelf

热门文章

  1. for i,x in enumerate() 函数解释
  2. 审计日志在分布式系统中的应用
  3. welearn自动答题脚本
  4. 单片机开发综合实验装置
  5. [LeetCode]3Sum Closest
  6. php smtp reply,php – 在电子邮件中设置replyTo字段
  7. QCustomPlot的使用之四-响应鼠标移动和弹起事件
  8. 软件测试(二)-经典测试技术-静态测试
  9. 【前端面试知识题】- 3. HTML CSS
  10. x86汇编_移位和循环移位指令简介_笔记46