传教士过河问题

三个传教士和三个食人土著要通过一条小船过河,这条船每次只能载两个人,同时,无论在河的两岸还是船上,只要食人土著的人数多于传教士的人数,食人土著就会吃掉传教士。问能否在传教士不被吃的情况下、让传教士和食人土著过河。

这道题没有什么明确的算法可以解决,只能考虑暴力方法解决——回溯法。
可以用(#左岸传教士,#左岸食人土著, #右岸传教士, #右岸食人土著, 船是否在左岸)这个元组来表示每个时刻的状态。那么起始状态就是(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();
    }
}

经过运行,共有四种方法安全过河。

猜你喜欢

转载自blog.csdn.net/guojunxiu/article/details/82144892