前言: 《野人与传教士》小游戏笔者在很小的时候就玩过, 当时无论怎么玩都过去. 正好最近疫情期间在家无聊, 又恰巧最近在学习搜索算法. 发现这个东西原来用代码实现起来非常简单. 就尝试了一下. 又想试试CSDN的博客是怎么写出来的, 以备未来大四或是无聊之时写写博客打下基础, 特以此篇为尝试
1 概述
算法非常简单, 只要注意到几点就可以:
- BFS的实现(在此属于一种盲目搜索. 无信息搜索)
- 访问过的节点一定不能再次入栈! 不然搜索时间会数量级的增加!
- 不要漏掉移动的情况.
- 注意每一种状态的表达方式.(3,3,1) 代表右岸野人数量, 右岸传教士数量, 船是否在右岸
2 代码
代码写的超级丑= =, 下文之处几点可改进之处和疑惑, 有待后续改进.
//解决小游戏:http://www.4399.com/flash/77287_2.htm
//三个野人,三个传教士, 船在右岸, 怎么全部过河?
#include<iostream>
#include<queue>
#include<cstring>
#include<set>
using namespace std;
struct Node{
int r_cannibal; //右岸野人数量
int r_missionary; //右岸传教士数量
int boat; //为1代表在右岸
Node* pre; //父节点,便于回溯
int step;
Node(int c,int m,int b,Node* p,int s):r_cannibal(c),r_missionary(m),boat(b),pre(p),step(s){}
Node(Node* n){
r_cannibal = n->r_cannibal;
r_missionary = n->r_missionary;
boat = n->boat;
pre = n->pre;
}
bool operator < (const Node& other)const{
if(this->r_cannibal==other.r_cannibal){
if(this->r_missionary==other.r_missionary){
return this->boat<other.boat;
}else{
return this->r_missionary<other.r_missionary;
}
}else{
return this->r_cannibal < other.r_cannibal;
}
return other.r_cannibal==r_cannibal&&other.r_missionary==r_missionary&&other.boat==boat;
}
};
queue<Node*> q;
//bool visted[4][4][2];
set<Node> visited;
bool isSafe(Node* now){
int rc = now->r_cannibal;
int lc = 3 - rc;
int rm = now->r_missionary;
int lm = 3 - rm;
if((rm<rc&&rm>0)||(lm<lc&&lm>0))
return false;
else
return true;
}
bool isGoal(Node* now){
int rc = now->r_cannibal;
int rm = now->r_missionary;
if(rc==0&&rm==0)
return true;
else
return false;
}
inline bool isVisted(Node* now){
// return false;
int insize = visited.size();
visited.insert(*now);
int outsize = visited.size();
if(insize == outsize){
return true;
}else{
return false;
}
}
void showNode(Node* now){
cout<<"("<<now->r_cannibal<<","<<now->r_missionary<<","<<now->boat<<")"<<" step->"<<now->step<<endl;
}
inline void setVisted(Node* now){
visited.insert(*now);
}
Node* searchNode(Node* start){
setVisted(start);
q.push(start);
while(!q.empty()){
Node* now = q.front();
q.pop();
showNode(now);
int rc = now->r_cannibal;
int lc = 3 - rc;
int rm = now->r_missionary;
int lm = 3 - rm;
Node* temp;
//船在右岸
if(now->boat==1){
if(rc>=1){
temp = new Node(now);
temp->r_cannibal-=1; //移动一个野人
temp->boat = 0;
temp->pre = now;
temp->step = now->step+1;
if(isGoal(temp))
return temp;
if(isSafe(temp)&&!isVisted(temp))
{
setVisted(temp);
q.push(temp);
}
else
delete temp;
}
if(rm>=1){
temp = new Node(now);
temp->r_missionary-=1; //移动一个传教士
temp->boat = 0;
temp->pre = now;
temp->step = now->step+1;
if(isGoal(temp))
return temp;
if(isSafe(temp)&&!isVisted(temp))
{
setVisted(temp);
q.push(temp);
}
else
delete temp;
}
if(rm>=1&&rc>=1){
temp = new Node(now);
temp->r_missionary-=1; //移动一个传教士
temp->r_cannibal-=1; //移动一个野人
temp->boat = 0;
temp->pre = now;
temp->step = now->step+1;
if(isGoal(temp))
return temp;
if(isSafe(temp)&&!isVisted(temp))
{
setVisted(temp);
q.push(temp);
}
else
delete temp;
}if(rm>=2){ //移动两个传教士去左岸
temp = new Node(now);
temp->r_missionary-=2; //移动2个传教士
temp->boat = 0;
temp->pre = now;
temp->step = now->step+1;
if(isGoal(temp))
return temp;
if(isSafe(temp)&&!isVisted(temp))
{
setVisted(temp);
q.push(temp);
}
else
delete temp;
}if(rc>=2){ //移动两个野人去左岸
temp = new Node(now);
temp->r_cannibal-=2; //移动2个野人
temp->boat = 0;
temp->pre = now;
temp->step = now->step+1;
if(isGoal(temp))
return temp;
if(isSafe(temp)&&!isVisted(temp))
{
setVisted(temp);
q.push(temp);
}
else
delete temp;
}
}
//船在左岸
else if(now->boat==0){
if(lc>=1){
temp = new Node(now);
temp->r_cannibal+=1; //移动一个野人
temp->boat = 1;
temp->pre = now;
temp->step = now->step+1;
if(isGoal(temp))
return temp;
if(isSafe(temp)&&!isVisted(temp))
{
setVisted(temp);
q.push(temp);
}
else
delete temp;
}
if(lm>=1){
temp = new Node(now);
temp->r_missionary+=1; //移动一个传教士
temp->boat = 1;
temp->pre = now;
temp->step = now->step+1;
if(isGoal(temp))
return temp;
if(isSafe(temp)&&!isVisted(temp))
{
setVisted(temp);
q.push(temp);
}
else
delete temp;
}
if(lm>=1&&lc>=1){
temp = new Node(now);
temp->r_missionary+=1; //移动一个传教士
temp->r_cannibal+=1; //移动一个野人
temp->boat = 1;
temp->pre = now;
temp->step = now->step+1;
if(isGoal(temp))
return temp;
if(isSafe(temp)&&!isVisted(temp))
{
setVisted(temp);
q.push(temp);
}
else
delete temp;
}if(lm>=2){ //移动两个传教士去右岸
temp = new Node(now);
temp->r_missionary+=2; //移动2个传教士
temp->boat = 1;
temp->pre = now;
temp->step = now->step+1;
if(isGoal(temp))
return temp;
if(isSafe(temp)&&!isVisted(temp))
{
setVisted(temp);
q.push(temp);
}
else
delete temp;
}if(lc>=2){ //移动两个野人去右岸
temp = new Node(now);
temp->r_cannibal+=2; //移动2个野人
temp->boat = 1;
temp->pre = now;
temp->step = now->step+1;
if(isGoal(temp))
return temp;
if(isSafe(temp)&&!isVisted(temp))
{
setVisted(temp);
q.push(temp);
}
else
delete temp;
}
}
}
return NULL;
}
void showLine(Node* goal){
Node* preN;
preN = goal->pre;
if(preN!=NULL)
showLine(preN);
cout<<"->";
showNode(goal);
}
int main(){
int num = 3;
Node* start = new Node(num,num,1,NULL,0);
Node* goal = searchNode(start);
if(goal==NULL){
cout<<"无解!"<<endl;
return 0;
}
cout<<"查找成功!"<<endl;
showLine(goal);
return 0;
}
说明:
- 使用set集合自动去掉重复的访问节点, 需要实现Node结构体的小于号重载.
- 输出是使用递归反向输出, 可直接正向输出路径
- 这个说明就是为了测试一下CSDN的序号功能
3 疑惑与改进
3.1 个人疑惑备忘
-
为什么船容纳两个人的时候传教士与野人的数量大于5之后就没有解了? 是Bug 还是可以用数学证明
-
是否最优? 应该是最优, 这种应该属于无信息、代价一致搜索. 应该是具有完备性的. 所的路径应该是最短的一条, 但如何证明?
-
set, 函数内声明实例, 等等. 何时引用何时用指针还有些乱, 没有细致的梳理, 我的写法不一定什么时候就出现浅拷贝的问题了. 有时间总结一下什么时候引用好一点什么时候指针好一点
3.2 改进
写代码时候仅是为了找到一条路, 没想着后续修改的问题, 未来可做如下改进= =, 虽然也不会改, 自我批评一下
- 船的容纳人数可扩展性极差. 代码大量冗余. 如果想改为三个人坐船就需要大量修改代码
可以将人数判断写到isSafe函数里面, 然后根据人数的多少写个双重循环, 遍历所有的情况.
- 不人性化= =, 可以加入一个是如何进入栈的, 就可以更明显的知道进入一步之后改怎么做
4 其他
我想测试一下引用块的使用 ───鲁迅
<-公式编辑器还蛮强的
就先这么多~
希望等毕业了能多写点博客, 省得自己闲得慌= =.
Best Wish!