问题:一个农夫带着一匹狼、一只羊、一颗白菜要过河,只有一条船而且农夫每次最多只能带一个动物或物品过河,并且当农夫不在的时候狼会吃羊,羊会吃白菜,列出所有安全将所有动物和物品带过河的方案。


思路分析:显然这不是一个最优解的问题,最容易想到的方法就是遍历了,设法用遍历列举出所有可能出现的情况,根据条件选取出其中所有正确的解路径。把每个状态看作一个节点的话,那么节点的子节点可以设计为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)