人、狼、羊、白菜过河问题(广度搜索)
大家好,我是小白莲,今天小白莲给大家分享的是广度搜索的应用之人、狼、羊、白菜过河问题,相信大家在小时候都听过这个有趣的思考题,没听过也没关系,大概是这样的:
初始状态:人、狼、羊、白菜都在左岸
目的:人、狼、羊、白菜都安全到达右岸
限制条件:
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