三个传教士和三个食人土著要通过一条小船过河,这条船每次只能载两个人,同时,无论在河的两岸还是船上,只要食人土著的人数多于传教士的人数,食人土著就会吃掉传教士。问能否在传教士不被吃的情况下、让传教士和食人土著过河。
这道题没有什么明确的算法可以解决,只能考虑暴力方法解决——回溯法。
可以用(#左岸传教士,#左岸食人土著, #右岸传教士, #右岸食人土著, 船是否在左岸)这个元组来表示每个时刻的状态。那么起始状态就是(3, 3, 0, 0, true), 我们的目标状态是(0, 0, 3, 3, false)。
做法很像倒水问题,唯一的难点在于回溯。使用队列只需要在回溯的时候将队尾的元素移除即可,比较好的解决了这个问题。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
class CrossRiver
{
public static int SUM = 3;
public static int BOATLOAD = 2;
List<State> states = new List<State>();
HashSet<State> visited = new HashSet<State>();
struct State
{
public int nLeftMissionary;
public int nLeftCannibal;
public int nRightMissionary;
public int nRightCannibal;
public bool isBootLeft;
//判断当前状态是否合法
public bool isValid()
{
return (nLeftMissionary==0 ? true : nLeftMissionary>=nLeftCannibal) && (nRightMissionary==0 ? true : nRightMissionary>=nRightCannibal);
}
public State(int nLeftMissionary, int nLeftCannibal, int nRightMissionary, int nRightCannibal, bool isBootLeft)
{
this.nLeftMissionary = nLeftMissionary;
this.nLeftCannibal = nLeftCannibal;
this.nRightMissionary = nRightMissionary;
this.nRightCannibal = nRightCannibal;
this.isBootLeft = isBootLeft;
}
public State (State s)
{
this.nLeftMissionary = s.nLeftMissionary;
this.nLeftCannibal = s.nLeftCannibal;
this.nRightMissionary = s.nRightMissionary;
this.nRightCannibal = s.nRightCannibal;
this.isBootLeft = s.isBootLeft;
}
//当前状态是否为目标状态
public bool isDone()
{
return nRightMissionary == SUM && nRightCannibal == SUM && isBootLeft == false;
}
}
public void Search()
{
State currentState = states[states.Count - 1];
if (currentState.isDone())
{
foreach (State s in states)
{
Console.WriteLine("({0},{1},{2},{3},{4})", s.nLeftMissionary, s.nLeftCannibal, s.nRightMissionary, s.nRightCannibal, s.isBootLeft);
}
Console.WriteLine("**********************************************");
}
//有两个运送方向,所以需要根据运送方向来改变传送的数量以及正负号(其实就是为了减少重复代码)
int nTransferMissionary = currentState.isBootLeft ? currentState.nLeftMissionary : currentState.nRightMissionary;
int nTransferCannibal = currentState.isBootLeft ? currentState.nLeftCannibal : currentState.nRightCannibal;
int sign = currentState.isBootLeft ? 1 : -1;
for (int i = 0;i <= Math.Min(BOATLOAD, nTransferMissionary); ++ i)
for (int j = 0; j <= Math.Min(BOATLOAD-i, nTransferCannibal); ++j)
{
if (i == 0 && j == 0) continue;
State next = new State(currentState);
next.nLeftMissionary -= sign * i;
next.nLeftCannibal -= sign * j;
next.nRightMissionary += sign * i;
next.nRightCannibal += sign * j;
next.isBootLeft = !next.isBootLeft;
if (next.isValid() && !visited.Contains(next))
{
states.Add(next);
visited.Add(next);
Search();
states.RemoveAt(states.Count - 1);
visited.Remove(next);
}
}
}
static void Main(string[] args)
{
State initState = new State(3, 3, 0, 0, true);
CrossRiver cr = new CrossRiver();
cr.states.Add(initState);
cr.visited.Add(initState);
cr.Search();
Console.ReadKey();
}
}
经过运行,共有四种方法安全过河。