c實現農夫過河問題
阿新 • • 發佈:2019-01-30
問題描述:一個農夫帶著一隻狼、一隻羊和一棵白菜,身處河的南岸。他要把這些東西全部運到北岸。問題是他面前只有一條小船,船小到只能容下他和一件物品,另外只有農夫能撐船。另外,因為狼能吃羊,而羊愛吃白菜,所以農夫不能留下羊和白菜或者狼和羊單獨在河的一邊,自己離開。請問農夫該採取什麼方案才能將所有的東西運過河呢?
思路:此題與通常的過河問題不同之處是,不考慮過河時與物品花費的時間,而是考慮河兩岸物品是否能和諧相處的問題。
通常定義四元組:(農夫,狼,羊,菜),出發點南岸和終點北岸用狀態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; }