农夫过河 (BFS)(队列)

1 、问题描述

要求设计实现农夫过河问题(农夫带着一只狼,一只养,一棵白菜,一次只能带一个东西)如何安全过河。

 

2 、问题的解决方案:

可以用栈与队列、深度优先搜索算法及广度优先搜索算法相应的原理去解决问题。

1)   实现四个过河对象(农夫、白菜、羊和狼)的状态,可以用一个四位二进制数来表示,0表示未过河,1表示已经过河了。

2)   过河的对象必须与农夫在河的同一侧,可以设计函数来判断。

3)   防止状态往复,即农夫将一个东西带过去又带回来的情况发生,需将所有可能的状态进行标定。

4)   可用深度优先搜索算法及广度优先搜索算法去解题。

分析:对于这道课程设计题我一开始并没有思路,在网上搜索了很多的方法,发现广度优先搜索相对好一些。运用的是位运算方法,位运算的方法在程序中的确运算方便且速度快,但不便于我们去理解,我们可以直接在定义成一个字符串进行操作对象状态,这样子相对好理解一些。

  (1)初始化

  过河状态按照题目的二进制数规定即可,我在这里规定0000中第一个0代表农夫的状态、第二个0代表狼的状态、第三个0代表羊的状态、最后一个0代表白菜的状态,由于01组成的二进制数过河状态一共有16中情况,定义一个route[16]的数组,用于标记已经走过的状态,顺便将标记值定义为前驱状态便于查找,初始化route数组为-1(都没有经过),先把四个对象都没过河的情况(0000)放入队列,同时标记route[0]为0 (0状态没有前驱)表示已经经过。

  (2)搜索

  将第一个状态入队后,从队头取出进行搜索。先考虑三种情况,农夫是否能带狼、羊、白菜过河,先判断农夫跟这三个对象是否在同一个地方。如果在同一个地方,再假设如果换位之后,其状态是否合法。即羊不会被狼吃掉,白菜不会被羊吃掉,同时这个假设的状态之前还没有经过。如果这些条件都成立,那么这种过河方式就可以使用,标记状态,并将他放入队尾。把这三种情况全部考虑完之后,还有一种情况就是农夫什么东西都不带过河,判断条件跟上面一样,成立就标记状态放入队尾。这样就把一次农夫过河的全部情况都考虑了,再从下次搜索中接着从对头出来的情况继续重复上面的搜索,直到找到能使状态为1111的情况,这一定是最先找到的情况。

  (3)输出步骤

  route详细记录了每个状态的前驱,我们从route[15]对应值找前驱,再从前驱找其对应前驱,直到找到route[0]为止。这样的话就倒序输出了农夫过河的最佳路径

  实现代码

  1 #include<string>
  2 #include<stdio.h>
  3 #include<iostream>
  4 #include<queue>
  5 #include<stdlib.h>
  6 using namespace std;
  7 queue<string>st;//定义一个队列用来存放合法状态
  8 string a="0000";
  9 string b,c;
 10 int route[16],temp,l,r;
 11 //0代表农夫 1代表狼 2代表羊 3代表菜
 12 bool check(char a1 ,char b1,char c1,char d1)//定义函数来检测合法状态
 13 {
 14     if((a1=='1'&&b1=='1'&&c1=='1'&&d1=='1')||
 15        (a1=='0'&&b1=='0'&&c1=='0'&&d1=='0')||
 16        (a1=='1'&&b1=='0'&&c1=='1'&&d1=='0')||
 17        (a1=='1'&&b1=='1'&&c1=='1'&&d1=='0')||
 18        (a1=='1'&&b1=='1'&&c1=='0'&&d1=='1')||
 19        (a1=='1'&&b1=='0'&&c1=='1'&&d1=='1')||
 20        (a1=='0'&&b1=='1'&&c1=='0'&&d1=='0')||
 21        (a1=='0'&&b1=='0'&&c1=='1'&&d1=='0')||
 22        (a1=='0'&&b1=='0'&&c1=='0'&&d1=='1')||
 23        (a1=='0'&&b1=='1'&&c1=='0'&&d1=='1')
 24        )
 25     return true;
 26     else return false;
 27 }
 28 int bfs()
 29 {
 30     for(int i=0;i<=15;i++)//初始化route数组
 31         route[i]=-1;
 32     st.push(a);//将初始状态放入队尾
 33     route[0]=0;//标记初始状态以经过
 34     int test1=0,test2=0;//test1表示当前状态(十进制),test2表示假设状态(十进制)
 35     while(!st.empty()&&route[15]==-1)//如果队列不空或者都没有到达对岸就继续搜索
 36     {
 37         b=st.front();//获取队头状态
 38         test1=8*(b[0]-'0')+4*(b[1]-'0')+2*(b[2]-'0')+1*(b[3]-'0');//将状态转化为十进制
 39         st.pop();将队头状态弹出
 40         for(int i=1;i<=3;i++)//判断农夫是否能带三种对象过河
 41         {
 42             c=b;//为了不破坏状态用c来代替
 43             if(b[0]==b[i])//如果农夫和其中一个对象在同一个地方
 44             {
 45                 if(c[0]=='0')//如果是0就过河换为1
 46                 {
 47                     c[0]='1';
 48                     c[i]='1';
 49                 }
 50                 else//如果是1就过河换为0
 51                 {
 52                     c[0]='0';
 53                     c[i]='0';
 54                 }
 55                 test2=8*(c[0]-'0')+4*(c[1]-'0')+2*(c[2]-'0')+1*(c[3]-'0');//将假设状态转化为十进制
 56                 if(check(c[0],c[1],c[2],c[3])&&route[test2]==-1)//如果假设情况过河后状态是合法的同时这种状态还没有经过
 57                 {
 58                     st.push(c);//将这种状态放入队列中
 59                     route[test2]=test1;//标记这种状态已经经过
 60                 }
 61             }
 62         }
 63         c=b;//这种情况是考虑农夫不带任何东西过河,与上面判断情况相同
 64         if(c[0]=='0')
 65         {
 66             c[0]='1';
 67         }
 68         else
 69         {
 70             c[0]='0';
 71         }
 72         test2=8*(c[0]-'0')+4*(c[1]-'0')+2*(c[2]-'0')+1*(c[3]-'0');
 73         if(check(c[0],c[1],c[2],c[3])&&route[test2]==-1)
 74         {
 75             st.push(c);
 76             route[test2]=test1;
 77         }
 78     }
 79     if(route[15]!=-1)//如果最终全部都过了河,倒序输出过河步骤
 80     {
 81         cout<<"15   1111"<<endl;//15情况没有后继直接输出
 82         for(int i=15;i>0;i=route[i])
 83         {
 84             cout<<route[i]<<"   ";//输出该状态对应前驱
 85             if(route[i]<10)
 86             cout<<' ';
 87             switch(route[i])//输出该状态十进制数对应的二进制数
 88             {
 89                 case 0:cout<<"0000"<<endl;break;
 90                 case 1:cout<<"0001"<<endl;break;
 91                 case 2:cout<<"0010"<<endl;break;
 92                 case 3:cout<<"0011"<<endl;break;
 93                 case 4:cout<<"0100"<<endl;break;
 94                 case 5:cout<<"0101"<<endl;break;
 95                 case 6:cout<<"0110"<<endl;break;
 96                 case 7:cout<<"0111"<<endl;break;
 97                 case 8:cout<<"1000"<<endl;break;
 98                 case 9:cout<<"1001"<<endl;break;
 99                 case 10:cout<<"1010"<<endl;break;
100                 case 11:cout<<"1011"<<endl;break;
101                 case 12:cout<<"1100"<<endl;break;
102                 case 13:cout<<"1101"<<endl;break;
103                 case 14:cout<<"1110"<<endl;break;
104             }
105             if(i==0)
106                 break;
107         }
108     }
109 }
110 int main()
111 {
112     bfs();
113 }

 

posted on 2019-06-14 19:11  华盛顿砍倒樱桃树  阅读(804)  评论(1编辑  收藏  举报