C語言實現野人與傳教士過河問題
野人與傳教士過河問題
問題重述:
有三個傳教士和三個野人過河, 只有一條能裝下兩個人的船,在河的任何一方或者船上,如果野人的人數大於傳教士的人數,那麼傳教士就會有危險,採用何種渡河方法,可以安全過河。
演算法分析:
初始狀態:左岸,3野人,3傳教士;右岸, 0野人,0傳教士;船停在左岸,船上有0個人。
目標狀態:左岸,0野人,0傳教士;右岸, 3野人,3傳教士;船停在右岸,船上有0個人。
將整個問題抽象成怎樣從初始狀態經一系列的中間狀態從而達到目標狀態,狀態的改變是通過划船渡河來引發的。
根據要求,共得出以下5中可能的渡河方案:
(1)渡2傳教士
(2)渡2野人
(3)渡1野人1傳教士
(4)渡1傳教士
(5)渡1野人
本程式使用類來定義狀態結點,使用集合儲存狀態結點,使用遞迴的思想來尋找目標狀態。
程式詳細執行流程如下:
首先,包含狀態(首次為初始狀態)的結構體結點(已存入結構體陣列)傳入處理函式,然後判斷該傳入結點狀態是否為目標狀態,是則遍歷列印結構體陣列,列印完成之後,返回遞迴呼叫處,順序執行之後程式碼(此步驟關係到是否能找到所有過河路徑);否則繼續判斷是否該傳入結點已存在於結構體陣列當中,如存在,不再往下執行,返回遞迴呼叫處,順序執行之後程式碼;若不存在,則繼續判斷該傳入狀態的人數是否合理(是否出現人物數量小於0的情況等),若不合理,返回遞迴呼叫處,順序執行之後程式碼;若合理,則繼續判斷傳教士和野人人數限制條件,即在傳教士人數不為0的情況下,野人人數是否大於傳教士人數,若大於則出現吃人的情況,也就是說該傳入狀態也不合理,則返回遞迴呼叫處,順序執行之後程式碼;若不滿足大於條件,則說明該狀態是路徑轉態,也就是合理的,那麼進行五種渡河方案的依次變換,首先為第一種渡河方案,兩個傳教士過河(注意:此處的5中渡河方案沒有固定順序,也可以是其他渡河方案),那麼對該傳入狀態的左岸和右岸的傳教士人數和野人人數進行增減(若為左岸到右岸,則左岸人數減,右岸人數加,此處有一個小技巧見本段末尾)。增減完成並改變船的狀態(使用正負一表示,正一為左岸,負一為右岸)以後就產生了一個新的狀態,將該狀態存入結構體陣列,之後此處又遞迴呼叫處理函式,將新產生的轉態結點傳入,再次進行上述條件限制判斷。若在該判斷途中被返回至遞迴呼叫處,說明該狀態不合理,則此時將已經存入結構體陣列的狀態結點移出結構體陣列,然後程式順序執行,進行下一個渡河方案的處理,也就是說,此時的處理是對上一個傳入結點的操作(因為剛傳入的已經移出了);若在判斷途中未被返回至遞迴呼叫處,也就是說,傳入的結點合理了,那麼又開始從第一種渡河方案開始對該傳入狀態進行操作。按照上述過程迴圈執行,直到出現目標狀態,回到本段開頭,遍歷結構體陣列,列印渡河路徑結點資訊。完成以後,返回遞迴呼叫處,順序執行之後程式碼,此後的操作是在尋找其他渡河路徑。原理為:由於該處理函式末尾存在return語句(關鍵),所以在找到目標狀態並返回之後,目標轉態結點同樣會被移出結構體陣列,然後在其上一個結點開始順序往下執行操作之後的一種渡河方案,檢視是否在該結點處,還有其他渡河方案可以達到目標狀態,若有則同樣按上述方法執行(列印輸出),若執行完後面的所有渡河方案,發現都沒有能夠達到目標狀態的結點,則會執行末尾的返回語句,返回之後,該狀態結點也會被移除(關鍵),那麼此時操作的狀態結點就是上上個結點狀態,對其進行其後的渡河方案操作。按照此法,不斷往後退,直到所有結點都被移除,此時說明已經完成所有渡河路徑的搜尋(深度)。至此,本程式的執行過程敘述完畢。
小技巧:從左岸到右岸,和從右岸到左岸的狀態變化是不一樣的,前者左岸的人數減,右岸的人數加;後者左岸的人數加,右岸的人數減。我們不應單獨再寫程式來處理,而是應該使用船的轉態帶入計算來處理,注意,此技巧在於船的轉態使用正負一來表示,而不應該是1和0,以及其他表示方法。為什麼這麼說?因為任何數乘以一,其本身都不會改變(有我也不會承認)。而正負號在此起到關鍵作用,我們使用正負一去乘以五種渡河方案的改變數值,從而得到的就是我們變換的正確結果,不論左岸右岸,都是正確合理的。
完整程式碼:
#include <stdio.h> #define MAX 100 //狀態 struct zt { int left_c; int right_c; int left_y; int right_y; int boat_location; }; struct zt ztarr[MAX]; int index=0; int numpass=0; int start_c,start_y; int handle(zt t) { //是否達到目標轉態 if( t.right_c == start_c && t.right_y == start_y) { numpass++; printf("\n找到第%d條路徑!\n",numpass); printf("左傳\t左野\t右傳\t右野\t船\n"); for(int i = 0; i <= index ; i++) { printf("%2d\t",ztarr[i].left_c); printf("%2d\t",ztarr[i].left_y); printf("%2d\t",ztarr[i].right_c); printf("%2d\t",ztarr[i].right_y); printf("%2d\t",ztarr[i].boat_location); printf("\n"); } //找到多條路徑的關鍵一 return 0; } //是否重複操作 for(int i = 0; i < index; i++) { if(t.left_c == ztarr[i].left_c && t.left_y == ztarr[i].left_y) { if(t.boat_location == ztarr[i].boat_location) { return 0; } } } //人數是否合理嗎 if(t.left_c < 0 || t.left_y < 0 || t.right_c < 0 || t.right_y < 0 ) { return 0; } //傳教士是否被吃 if((t.left_c < t.left_y && t.left_c != 0) || (t.right_c < t.right_y && t.right_c != 0) ) { return 0; } //定義一個臨時節點 struct zt tt; //兩個傳教士過河 tt.left_c = t.left_c - 2 * t.boat_location; tt.left_y = t.left_y; tt.right_c = t.right_c + 2 * t.boat_location; tt.right_y = t.right_y; tt.boat_location = ( -t.boat_location); index = index + 1; ztarr[index] = tt; handle(ztarr[index]); index = index - 1; //兩個野人過河 tt.left_c = t.left_c; tt.left_y = t.left_y - 2 * t.boat_location; tt.right_c = t.right_c ; tt.right_y = t.right_y + 2 * t.boat_location; tt.boat_location = ( -t.boat_location); index = index + 1; ztarr[index] = tt; handle(ztarr[index]); index = index-1; //一個野人,一個傳教士過河 tt.left_c = t.left_c - 1 * t.boat_location; tt.left_y = t.left_y - 1 * t.boat_location; tt.right_c = t.right_c + 1 * t.boat_location; tt.right_y = t.right_y + 1 * t.boat_location; tt.boat_location = ( -t.boat_location); index = index + 1; ztarr[index] = tt; handle(ztarr[index]); index = index-1; //一個傳教士過河 tt.left_c = t.left_c - 1 * t.boat_location; tt.left_y = t.left_y; tt.right_c = t.right_c + 1 * t.boat_location; tt.right_y = t.right_y; tt.boat_location = ( -t.boat_location); index = index + 1; ztarr[index] = tt; handle(ztarr[index]); index = index-1; //一個野人過河 tt.left_c = t.left_c; tt.left_y = t.left_y - 1 * t.boat_location; tt.right_c = t.right_c; tt.right_y = t.right_y + 1 * t.boat_location; tt.boat_location = ( -t.boat_location); index = index + 1; ztarr[index] = tt; handle(ztarr[index]); index = index-1; //找到多條路徑的關鍵二 return 0; } void main() { printf("請輸入初始傳教士人數:"); scanf("%d",&start_c); printf("請輸入初始傳教士人數:"); scanf("%d",&start_y); ztarr[index].left_c = start_c; ztarr[index].left_y = start_y; ztarr[index].right_c = 0; ztarr[index].right_y = 0; ztarr[index].boat_location = 1; handle(ztarr[index]); printf("已為您找到%d條過河路徑!並且已全部載入完畢!\n",numpass); }
結果圖:
下面是5個傳教士和3個野人的情況:
如有錯誤,歡迎指正!_