BFS 实现 野人与传教士过河问题 - 我的第一篇博客

前言:  《野人与传教士》小游戏笔者在很小的时候就玩过, 当时无论怎么玩都过去. 正好最近疫情期间在家无聊, 又恰巧最近在学习搜索算法. 发现这个东西原来用代码实现起来非常简单. 就尝试了一下.   又想试试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;
}

说明: 

  1. 使用set集合自动去掉重复的访问节点, 需要实现Node结构体的小于号重载.
  2. 输出是使用递归反向输出, 可直接正向输出路径
  3. 这个说明就是为了测试一下CSDN的序号功能

3  疑惑与改进

3.1   个人疑惑备忘

  • 为什么船容纳两个人的时候传教士与野人的数量大于5之后就没有解了? 是Bug 还是可以用数学证明

  • 是否最优? 应该是最优, 这种应该属于无信息、代价一致搜索. 应该是具有完备性的. 所的路径应该是最短的一条, 但如何证明?

  • set, 函数内声明实例, 等等. 何时引用何时用指针还有些乱, 没有细致的梳理, 我的写法不一定什么时候就出现浅拷贝的问题了.  有时间总结一下什么时候引用好一点什么时候指针好一点

3.2   改进

写代码时候仅是为了找到一条路, 没想着后续修改的问题,  未来可做如下改进= =, 虽然也不会改, 自我批评一下

  • 船的容纳人数可扩展性极差. 代码大量冗余. 如果想改为三个人坐船就需要大量修改代码

可以将人数判断写到isSafe函数里面, 然后根据人数的多少写个双重循环, 遍历所有的情况. 

  • 不人性化= =, 可以加入一个是如何进入栈的, 就可以更明显的知道进入一步之后改怎么做

 

4 其他

我想测试一下引用块的使用          ───鲁迅

 

 Latex?    <-公式编辑器还蛮强的

 

就先这么多~ 

希望等毕业了能多写点博客, 省得自己闲得慌= =.

Best Wish!

 

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蜗牛小瀚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值