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 #include3 #include 4 #include 5 #include 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"< //15情况没有后继直接输出 82 for(int i=15;i>0;i=route[i]) 83 { 84 cout< " ";//输出该状态对应前驱 85 if(route[i]<10) 86 cout<<' '; 87 switch(route[i])//输出该状态十进制数对应的二进制数 88 { 89 case 0:cout<<"0000"< break; 90 case 1:cout<<"0001"< break; 91 case 2:cout<<"0010"< break; 92 case 3:cout<<"0011"< break; 93 case 4:cout<<"0100"< break; 94 case 5:cout<<"0101"< break; 95 case 6:cout<<"0110"< break; 96 case 7:cout<<"0111"< break; 97 case 8:cout<<"1000"< break; 98 case 9:cout<<"1001"< break; 99 case 10:cout<<"1010"< break; 100 case 11:cout<<"1011"< break; 101 case 12:cout<<"1100"< break; 102 case 13:cout<<"1101"< break; 103 case 14:cout<<"1110"< break; 104 } 105 if(i==0) 106 break; 107 } 108 } 109 } 110 int main() 111 { 112 bfs(); 113 }