過河問題是數學上頗具趣味的題目,有夫妻過河、商人過河、農夫過河、人狗雞米過河問題。夫妻過河問題是 古老的阿拉伯智力題,敘述有三對夫妻要過河,小船只能載兩人,妻子不能在丈夫不在場時和別的異性渡河。 這類的數學模型稱為狀態轉移模型。把M個丈夫、F個妻子視為一種狀態,以(M,F)表示。其中M、F都是不大於三 的正整數。

S = { (0, 0), (0, 1), (0, 2), (0, 3), (1, 1), (2, 2), (3, 3), (3, 2), (3, 1), (3,0), (2, 1)}
有了允許狀態集合後,還要知道轉換的過程,如何從都在此岸的(3, 3)轉換成都在彼岸的(0, 0)。每
一次小船載人渡河都視為一次運算,第n次院算dn由第n+1個狀態Sn+1減去第n個狀態Sn決定。
dn = Sn+1-Sn
小船由此岸駛向彼岸時,此岸人數變少,當船駛回此岸,人數又變多,又因為小船不能載超過兩人,故dn必 然滿足下式:

以下是夫妻過河問題的一條路徑:


import java.util.*;
public class MB {
private byte boatAt; // 0表示在河左岸,1表示在河右岸
private byte mat[], bat[]; // 兩岸的傳教士和野人數量,最多127人
private short people; // 傳教士+野人總人數
public MB(int Msize, int Bsize) {
if (Msize>127 || Bsize>127) {
System.out.println("Current implementation support size <= 127");
System.exit(0);
}
mat = new byte[2];
bat = new byte[2];
mat[0] = (byte)Msize;
bat[0] = (byte)Bsize;
this.people = (byte)(Msize+Bsize); // 一開始所有的人都在左岸
}
public int getKey() {
return (boatAt<<14)+(mat[0]<<7)+bat[0]; // 左岸傳教士和野人數目,以及船在哪一邊三個參數可以唯一決定盤面
}
public boolean isSolution() {
return (mat[1]+bat[1])==people; // 所有人都到右岸就是解了
}
public boolean isValid() {
// 人數必須>=0,且任何一邊傳教士人數不得少於野人
return (mat[0]>=0 && mat[1]>=0 && bat[0]>=0 && bat[1]>=0 && (mat[0]==0 || mat[0]>=bat[0]) && (mat[1]==0 || mat[1]>=bat[1]));
}
public String getSide() { // 傳回此狀態船可以走的方向
return (boatAt==0) ? "往左邊" : "往右邊";
}
public void move(int m, int b) { // 移動m個傳教士,b個野人
byte otherSide = (boatAt==1) ? (byte)0 : (byte)1;
mat[boatAt] -= m;
mat[otherSide] += m;
bat[boatAt] -= b;
bat[otherSide] += b;
boatAt = otherSide;
}
private static final byte[][] branches = {{1,0},{0,1},{2,0},{0,2},{1,1}};
private static final String[] display = {"一個傳教士","一個野蠻人","兩個傳教士","兩個野蠻人","一個傳教士和一個野蠻人"};
private static int numSol;
private static boolean[] visited;
private static Vector history; // steps runs to this situation
private void expand() {
if (isSolution()) { // 如果目前的盤面是一組解
numSol++;
return;
}
for (int i=0; i < branches.length; i++) { // 展開五種可能性
move(branches[i][0], branches[i][1]); // 更改盤面成新的狀態
// 如果此盤面符合規則,而且沒有走過
if (isValid() && visited[getKey()]==false) {
visited[getKey()] = true; // 紀錄來過此盤面
history.add(display[i]+getSide()); // 紀錄此步驟
expand(); // recursive call to expand all possibles
history.remove(history.size()-1); // 移除此步驟
visited[getKey()] = false; // 移除來過此盤面
}
move(branches[i][0], branches[i][1]); // 改為原來的狀態
}
}
public static void findSolutions(int Msize, int Bsize) {
// 設定需要的資料結構
numSol = 0;
visited = new boolean[32786]; // 走過的盤面
MB init = new MB(Msize, Bsize); // 初始盤面
visited[init.getKey()] = true; // 目前的盤面已經來過了
history = new Vector();
init.expand();
System.out.println(numSol+" solutions found");
}
public static void main(String[] argv) throws Exception {
int Msize = 3;
int Bsize = 3;
System.out.println("Usage java MB Msize Bsize.\nThe default size is 3");
if (argv.length > 0) {
Msize = Integer.parseInt(argv[0]);
}
if (argv.length > 1) {
Bsize = Integer.parseInt(argv[1]);
}
findSolutions(Msize, Bsize);
}
}