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,以及其他表示方法。為什麼這麼說?因為任何數乘以一,其本身都不會改變(有我也不會承認)。而正負號在此起到關鍵作用,我們使用正負一去乘以五種渡河方案的改變數值,從而得到的就是我們變換的正確結果,不論左岸右岸,都是正確合理的。
動畫實現關鍵:
(1)在此,對於C#在動畫實現部分進行一些說明總結。初期,我是想通過在for迴圈列印資訊的時候實現按鈕的隱藏和顯示來實現動畫演示的,但是事實上並不是我想象的那麼簡單的,通過許多方法實踐後,發現在for迴圈裡面無法對按鈕的能否可見進行操作,而需要等待for迴圈完成以後,設定的部分按鈕才會顯示出來。這也就是說不能通過該方法來實現動畫演示了。後來我想到直接移動按鈕的方法,發現該方法可以在for迴圈當中進行操作,於是,實現了動畫演示的第一步。但是問題在於,我怎麼控制那個按鈕跳過河(左移),跳過河的按鈕不能繼續往前跳(左移),而只能往回跳(右移),但是指定按鈕來執行跳過河,這樣就會出現一個問題,例如,第一次,指定野人按鈕1,和野人按鈕2跳過河,之後,那個按鈕跳回來呢?(程式上實現不了)就算強行指定一個,跳回來以後,我需要再次執行兩個野人按鈕跳過河,同樣是執行剛才的程式碼,但是,剛才是指定的是野人按鈕1,和野人按鈕2,而現在應該是回來的野人按鈕和野人按鈕3過河。所以在程式上如果指定那個人物按鈕跳過河,是實現不了動畫演示的。
(2)現在,介紹程式是如何解決上述問題的。首先,我將所有狀態按鈕分左岸傳教士和野人,右岸傳教士和野人共計四組,分別新增進四個集合,表示不同岸的不同人物。 將左岸的集合大小直接複製給變數Ly(左岸的野人)和Lc(左岸的傳教士),而將右岸的集合大小減4(3-4)以後賦值給Ry(右岸的野人)和Rc(右岸的傳教士),也可以直接複製-1。這種思想類似於存在一個指向(最近寫程式頻繁受益於該思想),該指向在初始下指向左岸集合的末尾元素,而右岸沒有人,所以指向-1,當發生過河事件的時候,利用Ly、Lc、Ry、Rc來改變指向位置,例如在初始狀態下,過河連個野人,按照程式當中設計的結構,Ly會被連續兩次執行減一操作,二Ry會連續兩次執行加一操作。通過這種思想,我只用判斷岸上是否有人存在,並且按照已經產生的正確的過程狀態來進行按鈕移動,也就不會出錯,詳見程式(表達能力欠缺,可能未表達出正確思想,可參照程式理解)。
動畫演示關鍵程式(此處只演示左岸到右岸部分,詳見完整程式):
//左岸到右岸
if (list[i].getBoat_location() == 1)
{
cc.Left += 300;
if (Math.Abs(list[i+1].getLeft_c()-list[i].getLeft_c()) >= 1)
{
Lc--;
Rc++;
Left_c[Lc].Left += 300;
if (Math.Abs(list[i + 1].getLeft_c() - list[i].getLeft_c()) >= 2)
{
Lc--;
Rc++;
Left_c[Lc].Left += 300;
if (Math.Abs(list[i + 1].getLeft_c() - list[i].getLeft_c()) == 3)
{
Lc--;
Rc++;
Left_c[Lc].Left += 300;
}
}
}
if (Math.Abs(list[i+1].getLeft_y()-list[i].getLeft_y()) >= 1)
{
Ly--;
Ry++;
Left_y[Ly].Left += 300;
if (Math.Abs(list[i + 1].getLeft_y() - list[i].getLeft_y()) >= 2)
{
Ly--;
Ry++;
Left_y[Ly].Left += 300;
if (Math.Abs(list[i + 1].getLeft_y() - list[i].getLeft_y()) == 3)
{
Ly--;
Ry++;
Left_y[Ly].Left += 300;
}
}
}
}
程式執行效果圖:
完整程式碼如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
//定義過河狀態類
namespace C_Y_H
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
List<RiverSide> list = new List<RiverSide>();
List<Control> Left_c = new List<Control>();
List<Control> Left_y = new List<Control>();
List<Control> Right_c = new List<Control>();
List<Control> Right_y = new List<Control>();
int passNum = 0;
int Ly;
int Ry;
int Lc;
int Rc;
public int handle(RiverSide boat)
{
//是否達到目標轉態
if (boat.getRight_c() == 3 && boat.getRight_y() == 3)
{
passNum++;
textBox1.AppendText("\n\n已為您找到第" + passNum + "條路徑!\n\n");
textBox1.AppendText("\n\n左傳" + "\t" + "左野" + "\t" + "右傳" + "\t" + "右野" + "\t" + "船\n\n");
Ly = Left_y.Count;
Ry = -1;
Lc = Left_c.Count;
Rc = -1;
if(passNum != 1)
{
button9.Left -= 300;
button10.Left -= 300;
button11.Left -= 300;
button12.Left -= 300;
button13.Left -= 300;
button14.Left -= 300;
cc.Left -= 300;
}
for ( int i = 0; i < list.Count; i++)
{
System.Threading.Thread.Sleep(1000);
textBox1.AppendText(" " + list[i].getLeft_c() + "\t " + list[i].getLeft_y() + "\t " + list[i].getRight_c() + "\t " + list[i].getRight_y() + "\t" + list[i].getBoat_location()+"\n");
if (list[i].getBoat_location() == 1)
{
cc.Left += 300;
if (Math.Abs(list[i+1].getLeft_c()-list[i].getLeft_c()) >= 1)
{
Lc--;
Rc++;
Left_c[Lc].Left += 300;
if (Math.Abs(list[i + 1].getLeft_c() - list[i].getLeft_c()) >= 2)
{
Lc--;
Rc++;
Left_c[Lc].Left += 300;
if (Math.Abs(list[i + 1].getLeft_c() - list[i].getLeft_c()) == 3)
{
Lc--;
Rc++;
Left_c[Lc].Left += 300;
}
}
}
if (Math.Abs(list[i+1].getLeft_y()-list[i].getLeft_y()) >= 1)
{
Ly--;
Ry++;
Left_y[Ly].Left += 300;
if (Math.Abs(list[i + 1].getLeft_y() - list[i].getLeft_y()) >= 2)
{
Ly--;
Ry++;
Left_y[Ly].Left += 300;
if (Math.Abs(list[i + 1].getLeft_y() - list[i].getLeft_y()) == 3)
{
Ly--;
Ry++;
Left_y[Ly].Left += 300;
}
}
}
}
if (list[i].getBoat_location() == -1)
{
if (i != list.Count - 1)
{
cc.Left -= 300;
if (Math.Abs(list[i + 1].getRight_c() - list[i].getRight_c()) >= 1)
{
Left_c[Lc].Left -= 300;
Lc++;
Rc--;
if (Math.Abs(list[i + 1].getRight_c() - list[i].getRight_c()) >= 2)
{
Left_c[Lc].Left -= 300;
Lc++;
Rc--;
if (Math.Abs(list[i + 1].getRight_c() - list[i].getRight_c()) == 3)
{
Left_c[Lc].Left -= 300;
Lc++;
Rc--;
}
}
}
if (Math.Abs(list[i + 1].getRight_y() - list[i].getRight_y()) >= 1)
{
Right_y[Ry].Left -= 300;
Ry--;
Ly++;
if (Math.Abs(list[i + 1].getRight_y() - list[i].getRight_y()) >= 2)
{
Right_y[Ry].Left -= 300;
Ry--;
Ly++;
if (Math.Abs(list[i + 1].getRight_y() - list[i].getRight_y()) == 3)
{
Right_y[Ry].Left -= 300;
Ry--;
Ly++;
}
}
}
}
}
System.Threading.Thread.Sleep(200);
}
return 0;
}
//是否重複操作
for (int i = 0; i < list.Count -1; i++)
{
if (boat.getLeft_c() == list[i].getLeft_c() && boat.getLeft_y() == list[i].getLeft_y())
{
if (boat.getBoat_location() == list[i].getBoat_location())
{
return 0;
}
}
}
// 人數合理嗎
if (boat.getLeft_c() < 0 || boat.getLeft_y() < 0 || boat.getRight_c() < 0 || boat.getRight_y() < 0)
{
return 0;
}
// 傳教士被吃了沒
if ((boat.getLeft_c() < boat.getLeft_y() && boat.getLeft_c() != 0) || (boat.getRight_c() < boat.getRight_y() && boat.getRight_c() != 0))
{
return 0;
}
//定義一個空的狀態
RiverSide nullboat = new RiverSide();
// 兩個傳教士過河
nullboat.setLeft_c(boat.getLeft_c() - 2 * boat.getBoat_location());
nullboat.setLeft_y(boat.getLeft_y());
nullboat.setRight_c(boat.getRight_c() + 2 * boat.getBoat_location());
nullboat.setRight_y(boat.getRight_y());
nullboat.setBoat_location(-boat.getBoat_location());
list.Add(nullboat);
handle(nullboat);
list.RemoveAt(list.Count-1);
// 兩個野人過河
nullboat.setLeft_c(boat.getLeft_c());
nullboat.setLeft_y(boat.getLeft_y() - 2 * boat.getBoat_location());
nullboat.setRight_c(boat.getRight_c());
nullboat.setRight_y(boat.getRight_y() + 2 * boat.getBoat_location());
nullboat.setBoat_location(-boat.getBoat_location());
list.Add(nullboat);
handle(nullboat);
list.RemoveAt(list.Count - 1);
// 一個野人,一個傳教士
nullboat.setLeft_c(boat.getLeft_c() - 1 * boat.getBoat_location());
nullboat.setLeft_y(boat.getLeft_y() - 1 * boat.getBoat_location());
nullboat.setRight_c(boat.getRight_c() + 1 * boat.getBoat_location());
nullboat.setRight_y(boat.getRight_y() + 1 * boat.getBoat_location());
nullboat.setBoat_location(-boat.getBoat_location());
list.Add(nullboat);
handle(nullboat);
list.RemoveAt(list.Count - 1);
// 一個傳教士過河
nullboat.setLeft_c(boat.getLeft_c() - 1 * boat.getBoat_location());
nullboat.setLeft_y(boat.getLeft_y());
nullboat.setRight_c(boat.getRight_c() + 1 * boat.getBoat_location());
nullboat.setRight_y(boat.getRight_y());
nullboat.setBoat_location(-boat.getBoat_location());
list.Add(nullboat);
handle(nullboat);
list.RemoveAt(list.Count - 1);
// 一個野人過河
nullboat.setLeft_c(boat.getLeft_c());
nullboat.setLeft_y(boat.getLeft_y() - 1 * boat.getBoat_location());
nullboat.setRight_c(boat.getRight_c());
nullboat.setRight_y(boat.getRight_y() + 1 * boat.getBoat_location());
nullboat.setBoat_location(-boat.getBoat_location());
list.Add(nullboat);
handle(nullboat);
list.RemoveAt(list.Count - 1);
return 0;
}
private void button1_Click_1(object sender, EventArgs e)
{
Left_c.Add(button12);
Left_c.Add(button13);
Left_c.Add(button14);
Left_y.Add(button9);
Left_y.Add(button10);
Left_y.Add(button11);
Right_c.Add(button12);
Right_c.Add(button13);
Right_c.Add(button14);
Right_y.Add(button9);
Right_y.Add(button10);
Right_y.Add(button11);
RiverSide start = new RiverSide(3, 3, 0, 0, 1);
list.Add(start);
handle(start);
textBox1.AppendText("\n已為您找到所有過河路徑,並全部已載入完畢! ^_^");
}
}
}
\\狀態類:
public class RiverSide
{
private int left_c;
private int left_y;
private int right_c;
private int right_y;
private int boat_location;
public RiverSide()
{
// TODO 自動生成的建構函式存根
}
public int getLeft_c()
{
return left_c;
}
public void setLeft_c(int left_c)
{
this.left_c = left_c;
}
public int getLeft_y()
{
return left_y;
}
public void setLeft_y(int left_y)
{
this.left_y = left_y;
}
public int getRight_c()
{
return right_c;
}
public void setRight_c(int right_c)
{
this.right_c = right_c;
}
public int getRight_y()
{
return right_y;
}
public void setRight_y(int right_y)
{
this.right_y = right_y;
}
public int getBoat_location()
{
return boat_location;
}
public void setBoat_location(int boat_location)
{
this.boat_location = boat_location;
}
public RiverSide(int left_c, int left_y, int right_c, int right_y, int boat_location)
{
this.left_c = left_c;
this.left_y = left_y;
this.right_c = right_c;
this.right_y = right_y;
this.boat_location = boat_location;
}
}
程式缺陷:
(1)只適用於三個傳教士和三個野人渡河的情況。
(2)部分程式碼可能較為繁瑣,重用性較差。
程式下載地址(解壓即可執行)(平臺:vs):https://download.csdn.net/download/qq_36260974/10790850
如有錯誤,歡迎指正! _