问题:一个农夫带着一匹狼、一只羊、一颗白菜要过河,只有一条船而且农夫每次最多只能带一个动物或物品过河,并且当农夫不在的时候狼会吃羊,羊会吃白菜,列出所有安全将所有动物和物品带过河的方案。
思路分析:显然这不是一个最优解的问题,最容易想到的方法就是遍历了,设法用遍历列举出所有可能出现的情况,根据条件选取出其中所有正确的解路径。把每个状态看作一个节点的话,那么节点的子节点可以设计为4个,分别对应的情况是:农夫单独过河、农夫带狼过河、农夫带羊过河、农夫带白菜过河,则所有的情况顶多构成一棵满4叉树。因为其中有的状态是不合法的,可以通过约束进行裁剪,实际这棵4叉树会小得多。问题解的模型就确定下来了,接下来是需要用一种搜索方法找到树中所有合法的解路径,我用的是深度优先搜索对这棵4叉树进行路径搜索,当搜索到的节点为最终目标节点时,那么从起始节点到该目标节点经过的路径就是一个解路径,直到搜索完这棵树后就能找到所有的解路径了。
tips:
1、为了程序能够便于表示,状态可以用4位二进制数来表示,0000(int类型为0)代表初始状态,从左往右每一位依次表示农夫、狼、羊、白菜,0表示在起始岸,1代表在目的岸,则目标状态为1111(int类型为15)。故用整型0-15这16个数字就可以表示所有状态了。
2、当程序进行深度优先搜索时,需要记录下该路径经过的节点(状态),因为节点(状态)是不能重复的,重复的情况没有意义,比如开始农夫把羊带到对岸,然后又把羊带回来,则此时重复了起始状态这就没有任何意义,而且也会让程序无限循环。所以用一个int route[16]数组就可以记录任何一条路径(因为一条路径顶多占满所有16个状态),数组的下标就表示该状态,数组的内容就记录上一个节点(状态)即其下标,比如route[15]=5表示节点(状态)15(即目标状态1111)上一个节点(状态)是5(即0101),再翻译白一点就是最后一步为农夫带羊过河(0101至1111)。
C语言代码:
#include<stdio.h>
int famerLocation(int currentLocation) //判断农民的位置
{
if((currentLocation&8)==8)
{
return 1;
}
else
{
return 0;
}
}
int wolfLocation(int currentLocaiton) //判断狼的位置
{
if((currentLocaiton&4)==4)
{
return 1;
}
else
{
return 0;
}
}
int goatLocation(int currentLocation) //判断羊的位置
{
if((currentLocation&2)==2)
{
return 1;
}
else
{
return 0;
}
}
int cabbageLocation(int currentLocation) //判断白菜的位置
{
if((currentLocation&1)==1)
{
return 1;
}
else
{
return 0;
}
}
int judgeSafe(int currentLocation) //判断当前状态是否安全
{ //安全返回1,不安全返回0
int a,b,c,d;
a=famerLocation(currentLocation);
b=wolfLocation(currentLocation);
c=goatLocation(currentLocation);
d=cabbageLocation(currentLocation);
if(a!=b&&b==c) //当农夫和狼位置不同而狼和羊位置相同时不安全
{
return 0;
}
else if((a!=c)&&(c==d)) //当农夫和羊位置不同而羊和白菜位置相同时不安全
{
return 0;
}
else
{
return 1;
}
}
void printRoute(int route[16]) //根据一个路径数组输出一条路径
{
int location=15;
while(location!=-2)
{
printf("%d ",location);
location=route[location];
}
printf("\n");
}
void process(int route[16],int currentLocation) //算法核心,输入要处理的路径route[16]和该路径当前最新位置currentLocation
{
if(route[15]==-1) //如果该路径还未到达目的状态
{
int mover;
for(mover=1;mover<=8;mover<<=1) //依次带不同的动物或单独过河过河4种方案
{
if(((currentLocation&8)!=0)==((currentLocation&mover)!=0)) //如果农夫和要带的动或物在同一侧
{
int nextLocation=currentLocation^(8|mover); //用异或运算表示出过河后的状态
if(judgeSafe(nextLocation)&&route[nextLocation]==-1) //判断下一个状态是否安全并且是否产生重复
{
int nextRoute[16],i;
for(i=0;i<16;i++) //用一个新的数组先拷贝当前的路径
{
nextRoute[i]=route[i];
}
nextRoute[nextLocation]=currentLocation; //然后再记录下这新的一步
process(nextRoute,nextLocation); //用递归(深度优先搜索)再以同样方式处理这个新的路径和最新位置
}
}
}
}
else //该路径到达了目标状态
{
printRoute(route); //输出该路径
}
}
int main()
{
int route[16];
int i;
for(i=1;i<16;i++) //初始化路径,未经历的状态记录-1
{
route[i]=-1;
}
route[0]=-2; //起始状态已经经历过且没有前驱节点记录为-2
process(route,0); //处理该条起始路径即可
}
程序运行结果:
15 5 13 1 11 2 10 0
15 5 13 4 14 2 10 0
找到2个安全的方案,把它翻译过来就是:
1、0(0000)—(农夫带羊过河)—10(1010)—(农夫单独返回)—2(0010)—(农夫带白菜过河)—11(1011)—(农夫带羊返回)—1(0001)—(农夫带狼过河)—13(1101)—(农夫单独返回)—5(0101)—(农夫带羊过河)—15(1111)
1、0(0000)—(农夫带羊过河)—10(1010)—(农夫单独返回)—2(0010)—(农夫带狼过河)—14(1110)—(农夫带羊返回)—4(0100)—(农夫带白菜过河)—13(1101)—(农夫单独返回)—5(0101)—(农夫带羊过河)—15(1111)