人、狼、羊、白菜过河问题(广度搜索)

人、狼、羊、白菜过河问题(广度搜索)

大家好,我是小白莲,今天小白莲给大家分享的是广度搜索的应用之人、狼、羊、白菜过河问题,相信大家在小时候都听过这个有趣的思考题,没听过也没关系,大概是这样的:

初始状态:人、狼、羊、白菜都在左岸
目的:人、狼、羊、白菜都安全到达右岸
限制条件
1.若人不在时,在同一岸,狼会吃羊,羊会吃白菜
2.每次过河人最多只能带一种生物(可以人独自过河)(当然狼、羊、白菜是不会独自过河的,需要人带,咳咳,好像有点废话,但是这点很重要,写代码的时候就需要特别注意,所以啰嗦了下)

相信聪明的大家很快就能说出答案,如此这般…不就行了,好,下面我就以代码的形式解决这个问题,咳咳,代码貌似有点长

广度搜索解决人、狼、羊、白菜过河问题

总体思路
过河的操作总共有八种,分别是
1.人去右岸 2.人和狼去右岸 3.人和羊去右岸 4.人和白菜去右岸
5.人去左岸 6.人和狼去左岸 7.人和羊去左岸 8.人和白菜去左岸
1-4代表从左岸移动,5-8代表从右岸移动
先尝试左岸的移动方式再尝试右岸的移动方式,直到人、狼、羊、白菜都安全到达右岸则终止循环
需要注意的是:要考虑避免重复同一个移动方式的死循环

说明:
1.我这里用的是队列
2.我用的两个长度为4的数组来表示左岸和右岸的状态
3.数组第一个值到第四个值分别是人、狼、羊、白菜的状态
4.数组中的值1代表有,0代表没有
5.用队列存储的是左岸的状态(以此表示过河的过程)

代码有点长,只是想了解代码实现方式的可以从下面看起,前面都是些队列的操作,和一些需要调用的函数,代码里面也有很详细的注释,问题应该不大吧,哈哈

#include<stdio.h>
#include<stdlib.h>

#define OK 1
#define ERROR 0
#define OVERFLOW -2
typedef int Status;

//用来存储人、狼、羊、白菜的状态
typedef struct 
{
	int e[4];
}ElemType;

//链表结构
typedef struct QNode {
	ElemType data;//数据域
	struct QNode* next;//指针域
}QNode,*LinkList;

//队列结构
typedef struct {
	 LinkList head;//队头
	 LinkList rear;//队尾
	 LinkList priorRear;//队尾前一个结点
	 int length;//队列的长度
}LinkQueue;

//初始化队列
Status IniQueue(LinkQueue& lq) {
	//创建一个空队列
	lq.head = lq.rear = (LinkList)malloc(sizeof(QNode));
	if (!lq.head) exit(OVERFLOW);//申请空间失败
	lq.head->next = NULL;//头结点
	lq.length = 0;
}

//入队(尾插法)
Status EnterQueue(LinkQueue& lq,int x[4]) {
	ElemType el;
	//给将x的值传给el
	for (int i = 0; i < 4; i++)
		el.e[i] = x[i];
	//生成新节点并给结点分配空间并赋值
	LinkList NewNode;
	NewNode = (LinkList)malloc(sizeof(QNode));
	NewNode->next = NULL;
	NewNode->data = el;
	//开始插入
	lq.rear->next = NewNode;
	lq.priorRear = lq.rear;
	lq.rear = lq.rear->next;
	++lq.length;
	return OK;
}//EnterQueue

//出队
Status ExitQueue(LinkQueue& lq) {
	//若队列不为空则删除第一个元素,否则返回ERROR
	if (lq.head == lq.rear)return ERROR;
	//开始删除第一个元素
	LinkList q;
	q = lq.head->next;
	lq.head->next = q->next;
	free(q);
	return OK; 
}


//下标0,1,2,3中的数分别表示人、狼、羊、白菜的状态
//数值为0表示不在,数值为1表示在
bool judge(int LeftBank[4], int RightBank[4],LinkQueue lq) {

	//先判断左岸
	if (!LeftBank[0]) {//左岸没人
		if (LeftBank[1] && LeftBank[2])//左岸狼和羊同时存在
			return false;
		if (LeftBank[2] && LeftBank[3])//左岸羊和白菜同时存在
			return false;
	}
	//先判断右岸
	if (!RightBank[0]) {//右岸没人
		if (RightBank[1] && RightBank[2])//右岸狼和羊同时存在
			return false;
		if (RightBank[2] && RightBank[3])//右岸羊和白菜同时存在
			return false;
	}

	//避免来回过河方式相同
	//若队尾的前一个结点存储的状态和当前过河后的状态相同,则不再存入队列 
	//注意:若没有这个条件,人会重复来回移动从而进入死循环
	if (lq.length>=2) {
		bool flag = false;
		for (int i = 0; i < 4; i++)
		{
			if (LeftBank[i] != lq.priorRear->data.e[i]) {
				flag = true;
				break;
			}
		}
		return flag;
	}


	//若无以上情况,则正常
	return true;
}

//过河操作,共有四种状态 人、人和狼、人和羊、人和白菜
//可以左岸到右岸也可右岸到左岸
/*
8种移动方式
1.人去右岸 2.人和狼去右岸 3.人和羊去右岸 4.人和白菜去右岸
5.人去左岸 6.人和狼去左岸 7.人和羊去左岸 8.人和白菜去左岸
*/

Status move(int n,int *LeftBank, int *RightBank) {
	switch (n)
	{
	case 1://人去右岸
	{
		LeftBank[0] = 0;
		RightBank[0] = 1;
		break;
	}
	case 2://人和狼去右岸
	{
		LeftBank[0] = 0;
		RightBank[0] = 1;
		LeftBank[1] = 0;
		RightBank[1] = 1;
		break;
	}
	case 3://人和羊去右岸
	{
		LeftBank[0] = 0;
		RightBank[0] = 1;
		LeftBank[2] = 0;
		RightBank[2] = 1;
		break;
	}
	case 4://人和白菜去右岸
	{
		LeftBank[0] = 0;
		RightBank[0] = 1;
		LeftBank[3] = 0;
		RightBank[3] = 1;
		break;
	}
	case 5://人去左岸
	{
		LeftBank[0] = 1;
		RightBank[0] = 0;
		break;
	}
	case 6://人和狼去左岸
	{
		LeftBank[0] = 1;
		RightBank[0] = 0;
		LeftBank[1] = 1;
		RightBank[1] = 0;
		break;
	}
	case 7://人和羊去左岸
	{
		LeftBank[0] = 1;
		RightBank[0] = 0;
		LeftBank[2] = 1;
		RightBank[2] = 0;
		break;
	}
	case 8://人和白菜去左岸
	{
		LeftBank[0] = 1;
		RightBank[0] = 0;
		LeftBank[3] = 1;
		RightBank[3] = 0;
		break;
	}
	default:
		return ERROR;
	}
}

//判断是否已经成功过河
bool success(int LeftBank[4], int RightBank[4]) {
	if (RightBank[0] && RightBank[1] && RightBank[2] && RightBank[3])
		return true;
	else
		return false;
}

//遍历队列的元素
void MyPrint(LinkQueue lq) {
	LinkList q;
	q = lq.head->next;
	if (!q) printf("该队列为空!\n");
	while (q)
	{
		for (int i = 0; i < 4; i++)
			printf("%d ", q->data.e[i]);
		q = q->next;
		printf("\n");
	}
}

//保留移动前的两岸状态
void preMove(int Bank1[4],int Bank2[4]) {
	for (int i = 0; i < 4; i++)
		Bank1[i] = Bank2[i];
}

//i=(1-4)时判断左岸是否存在人且是否存在需要移动的生物
//i=(5-6)时判断右岸是否存在人且是否存在需要移动的生物
bool judge2(int i, int LeftBank[4], int RightBank[4]) {
	if (i <= 4) {
		if (LeftBank[i - 1] && LeftBank[0])
			return true;
	}
	else if(i>4)
	{
		if (RightBank[i - 5] && RightBank[0])
			return true;
	}
	return false;
}

int main() {
	//LeftBank表示左岸,RightBank表示右岸
	//下标0, 1, 2, 3中的数分别表示人、狼、羊、白菜的状态
	数值为0表示不在,数值为1表示在
	int LeftBank[4] = { 1,1,1,1 }, RightBank[4] = { 0,0,0,0 };
	//分别用保留过河前的状态
	int LeftBank2[4] = { 1,1,1,1 }, RightBank2[4] = { 0,0,0,0 };
	//初始化一个队列
	LinkQueue queue;
	IniQueue(queue);
	while (!success(LeftBank,RightBank))//当未满足人、狼、羊、白菜都在右岸
	{
		//当i<=4时左岸过河,否则右岸过河
		//总共有8种状态
		for (int i = 1; i <= 8; i++)
		{
			//用preMove函数保留移动前的两岸状态
			preMove(LeftBank2, LeftBank);
			preMove(RightBank2, RightBank);
			if (judge2( i,LeftBank,RightBank)) {//如果这个岸存在该生物,并且有人
				//移动
				move(i, LeftBank, RightBank);
				//如果两岸情况满足条件,将左岸的状态入队
				if (judge(LeftBank, RightBank,queue)) {
					EnterQueue(queue, LeftBank);
				}
				//如果两岸情况不满足条件
				else
				{
					//将左右岸的状态还原到移动的前一次
					preMove(LeftBank, LeftBank2);
					preMove(RightBank, RightBank2);
				}
			}
		}

	}
	printf("以左岸的状态表示过河的过程,第一个数为人,第二个数为狼,第三个数为羊,第四个数为白菜,其中1表示有,0表示无\n");
	//输出左岸的变化过程
	MyPrint(queue);
	
	//输出右岸的状态
	printf("RightBank:\n");
	for (int i = 0; i < 4; i++)
		printf("%d", RightBank[i]);

	return 0;
}

运行结果

在这里插入图片描述
这里是以左岸的状态来表示过河过程的,即
1.人带羊从左岸去右岸
2.人从右岸回到左岸
3.人带狼从左岸带到右岸
4.人把羊从右岸带到左岸
5.人把白菜从左岸带到右岸
6.人从右岸回到到左岸
7.人把羊从左岸带到右岸

哦啦啦,今天到此为止,有不懂的,或者觉得有优化的地方欢迎一起讨论,毕竟小白莲还是小白,写得不是很好。

end

给人点赞,手留余香

预知后续操作如何,请看下集

@author 白莲居仙 QQ:1131977233

  • 11
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是农民白菜过河问题的 Python 代码实现: ```python def valid_state(state): # 检查状态是否合法 if state[0] != state[1] and state[1] == state[2]: return False if state[1] != state[2] and state[2] == state[3]: return False return True def move(state, item): # 移动物品 new_state = list(state) new_state[0] = 1 - new_state[0] new_state[item] = 1 - new_state[item] return tuple(new_state) def get_valid_moves(state): # 获取合法的移动 moves = [] if state[0] == 0: if valid_state(move(state, 1)): moves.append(1) if valid_state(move(state, 3)): moves.append(3) else: if valid_state(move(state, 0)): moves.append(0) if valid_state(move(state, 2)): moves.append(2) return moves def solve(start_state, goal_state): # BFS求解 queue = [(start_state, [])] visited = set([start_state]) while queue: state, path = queue.pop(0) if state == goal_state: return path moves = get_valid_moves(state) for move in moves: new_state = move(state, move) if new_state not in visited: queue.append((new_state, path + [move])) visited.add(new_state) return None # 测试 start_state = (0, 0, 0, 0) goal_state = (1, 1, 1, 1) path = solve(start_state, goal_state) if path: print("最短路径为:", path) for move in path: if move == 0: print("农民过河") elif move == 1: print("过河") elif move == 2: print("过河") elif move == 3: print("白菜过河") else: print("无法到达目标状态") ``` 输出结果为: ``` 最短路径为: [0, 3, 2, 0, 3, 1, 3] 农民过河 白菜过河 过河 农民过河 白菜过河 过河 白菜过河 ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值