1. 程式人生 > >c實現農夫過河問題

c實現農夫過河問題

問題描述:一個農夫帶著一隻狼、一隻羊和一棵白菜,身處河的南岸。他要把這些東西全部運到北岸。問題是他面前只有一條小船,船小到只能容下他和一件物品,另外只有農夫能撐船。另外,因為狼能吃羊,而羊愛吃白菜,所以農夫不能留下羊和白菜或者狼和羊單獨在河的一邊,自己離開。請問農夫該採取什麼方案才能將所有的東西運過河呢?

思路:此題與通常的過河問題不同之處是,不考慮過河時與物品花費的時間,而是考慮河兩岸物品是否能和諧相處的問題。

通常定義四元組:(農夫,狼,羊,菜),出發點南岸和終點北岸用狀態0,1表示。開始時,他們都在出發點,因此可用(0,0,0,0)表示,最終他們都到達終點(如果可達)可以用(1,1,1,1)表示。因此,該問題可轉述成,求這兩個狀態能否可達,如果可達求出路徑的問題。為方便起見,用十六進位制數0000表示(0,0,0,0)。下圖表示可行的路徑。

通過廣度優先遍歷演算法實現。


具體程式碼如下:

#include "stdio.h"
#include "stdlib.h"
#define  MAXSIZE  20

typedef  struct {
  int   data[MAXSIZE];  /*佇列的儲存空間*/
  int front, rear;  /*隊頭隊尾指標*/
}SeqQueue,*PSeqQueue;

PSeqQueue  Init_SeqQueue( )
{   /*初始化佇列。返回值:新順序佇列指標,null表示失敗*/
           PSeqQueue  Q;
           Q=(PSeqQueue)malloc(sizeof(SeqQueue));
           if (Q) {
                  Q->front=0;
                 Q->rear=0;
           }
           return Q;
}

int  Empty_SeqQueue(PSeqQueue Q)
/*判空佇列。返回值:1表示為空,0表示非空*/
 {   if (Q && Q->front==Q->rear)
        return (1);
     else
        return (0);   
 }

 int   In_SeqQueue (PSeqQueue Q , int  x)
     {    /*入隊。返回值:1表示成功,-1表示隊滿溢位*/
          if ((Q->rear+1)%MAXSIZE==Q->front)
         {    printf("隊滿");
               return -1;   /*隊滿不能入隊*/
         }
         else 
        {     Q->rear=(Q->rear+1) % MAXSIZE;
               Q->data[Q->rear]=x;
               return 1;    /*入隊完成*/
        }
     }
int  Out_SeqQueue (PSeqQueue Q,int *x)
           {   /*出隊。返回值:1表示成功,-1表示隊空,出隊的元素儲存到*x  */
                 if (Empty_SeqQueue(Q)) {      
                     printf("隊空");
                     return -1;   /*隊空不能出隊*/
                }
                else
                {  Q->front=(Q->front+1) % MAXSIZE;
                    *x=Q->data[Q->front];
                     return 1;    /*出隊完成*/
                }
           }

int isSafe(int s)
{/*沒有農夫在場, 菜和羊共處或者羊和狼共處,均不安全。  */
	 if(
	   (  ((s&0x01)!=0) == ((s&0x02)!=0)) && (((s&0x08)!=0) != ((s&0x01)!=0)  ) 
	   || 
	   (  ((s&0x02)!=0) == ((s&0x04)!=0)) && (((s&0x08)!=0) != ((s&0x02)!=0)  )
	)
    return 0;
    else return 1;
} 

void farmerCrossing()
{
PSeqQueue Q;
int visited[16],i,status,nextStatus,who;

Q = Init_SeqQueue( ); 
for(i = 0;i < 16;i++)
	visited[i] = -1;

	status = 0x00;
	In_SeqQueue (Q , status);
visited[0] = 0; 
while(!Empty_SeqQueue(Q) && (visited[15]== -1))/* BFS */
{
Out_SeqQueue (Q,&status);
printf("\n");
printf("out%d  ",status);
for(who = 1; who <= 8; who <<= 1)/* 1(0000,0001),表示菜在終點。左移一位是0000,0010,表示羊在終點。  */
/* 再左移一位是0000,0100,表示狼在終點。最後再左移一位變成8(0000,1000),表示農夫在終點。  */
{
    if(((status&0x08) != 0) == ((status&who) != 0))/* 當前的狀態與用特定位置的1進行按位與操作, */
    /* 如 1010 & 1000 = 1000不等於零,只有 1010 & 0010=0010不等於零。 */
    /* 這樣其實就可以得知who是農夫、狼、羊還是菜,即得出農夫和誰在同一岸邊。注意,who不一定唯一。 */
    /* 如,第一次0000,四者都在左岸,可以從三個貨中選擇,也可以自己一個人過河。  */
    {
        nextStatus = status ^ (0x08 | who);/* 0x08|who 表示農夫想帶誰過河,再和當前狀態異或得出下一步狀態。 */
        /* 異或的妙處,與零異或保留原值,與一異或原值翻轉。這樣,比如當前狀態0000,由上面註釋知道此時, */
        /* 狀態改變為1001、1010、0100、0000。如部落格中的圖第二行所示。  */
         
        if(isSafe(nextStatus) && (visited[nextStatus]== -1))/* 去掉不安全的狀態,剪枝。如部落格中的圖第二行所示。  */
        {
            In_SeqQueue (Q , nextStatus);
            visited[nextStatus] = status;
            printf("in%d ",nextStatus);
        } 
    }
  
}
}
    nextStatus = 15;
   printf("\nthe reverse rout:");  
while(visited[nextStatus]!= -1)
   {
   	  printf("%d  ",visited[nextStatus]);
   	  nextStatus = visited[nextStatus];
   	  if(nextStatus == 0) exit(0);
   	  	  
   }
   
} 

int main(void)
{
 farmerCrossing();
  return 0;
}