妖怪与和尚过河问题(java语言)

妖怪与和尚过河问题

        有三个和尚(或传教士)和三个妖怪(或食人怪)过河,只有一条能装下两个人(和尚或妖怪)的船,在河的任何一方或者船上,如果妖怪的人数大于和尚的人数,那么和尚就会有被吃掉的危险。你能不能找出一种安全的渡河方法呢?

        这是一个很有意思的智力题,但是并不难,每次可以选择一个人或者两个人过河,只要保证在河的任何一边的和尚数量总是大于或等于妖怪的数量即可。这里先给出一种过河方法:

 

两个妖怪先过河,一个妖怪回来;

再两个妖怪过河,一个妖怪回来;

两个和尚过河,一个妖怪和一个和尚回来;

两个和尚过河,一个妖怪回来;

两个妖怪过河,一个妖怪回来;

两个妖怪过河。

 

过河的方法其实不止这一种,本文给出了一种求解全部过河方法的算法程序,可以通过穷举(状态树搜索)的方法得到全部四种过河方法。

 解决问题的思路

          题目的初始条件是三个和尚和三个妖怪在河的一边(还有一条船),解决问题后的终止条件是三个和尚和三个妖怪安全地过到河的对岸,如果把任意时刻妖怪和和尚的位置看作一个“状态”,则解决问题就是找到一条从初始状态变换到终止状态的路径。从初始状态开始,每选择一批妖怪或和尚过河(移动一次小船),就会从原状态产生一个新的状态,如果以人类思维解决这个问题,每次都会选择最佳的妖怪与和尚组合过河,使得它们过河后生成的新状态更接近最终状态,不断重复上述过程,直到得到最终状态。

        用计算机解决妖怪与和尚过河问题的思路也是通过状态转换,找到一条从初始状态到结束状态的转换路径。计算机不会进行理性分析,不知道每次如何选择最佳的过河方式,但是计算机擅长快速计算且不知疲劳,既然不知道如何选择过河方式,那就干脆把所有的过河方式都尝试一遍,找出所有可能的结果,当然也就包括成功过河的结果。

状态的数学模型

        本节探讨一下如何建立状态的数学模型。题目要求并不强调三个妖怪之间或三个和尚之间的差异,只是关注它们在和河两岸的数量,因此无需赋予和尚和妖怪过多的属性,只要用数值分别表示它们在和两岸的数量即可确定某个时刻的状态。除了和尚与妖怪的数量,还有一个很关键的因素也会影响到数学模型,那就是船的状态。例如某一时刻,本地河边有两个和尚和两个妖怪,对岸有一个和尚和一个妖怪,此时船在河这边和在河对岸就分别是两个完全不同的状态。和尚与妖怪的状态就是数值,船有两个状态,在本地河边(LOCAL)和在对岸(REMOTE),其实还有一个descIndex(int类型,即得到该状态所使用动作的标记,后面我们会讲到,先提前说一声)。我们用一个五元组来表示某个时刻的过河状态:[本地和尚数,本地妖怪数, 对岸和尚数,对岸妖怪数,船的位置]。用五元组表示的初始状态就是[3, 3, 0, 0, LOCAL],问题解决的过河状态是[0, 0, 3, 3, REMOTE]。

java代码封闭如下:

public class ItemState {
    int localMonster;
    int localMonk;
    int remoteMonster;
    int remoteMonk;
    BoatLocation boat;
    int descIndex;

    public ItemState() {
    }

    public ItemState(int localMonster, int localMonk, int remoteMonster,
            int remoteMonk, BoatLocation boat, int descIndex) {
        this.localMonster = localMonster;
        this.localMonk = localMonk;
        this.remoteMonster = remoteMonster;
        this.remoteMonk = remoteMonk;
        this.boat = boat;
        this.descIndex = descIndex;
    }

    @Override
    public boolean equals(Object obj) {
        // TODO Auto-generated method stub
        if (obj instanceof ItemState) {
            ItemState state = (ItemState) obj;
            if (this.localMonster == state.localMonster
                    && this.remoteMonk == state.remoteMonk
                    && this.remoteMonster == state.remoteMonster
                    && this.remoteMonk == state.remoteMonk
                    && this.boat == state.boat) {
                return true;
            }
        }
        return false;
    }

    @Override
    public String toString() {
        return "[" + localMonster + ", " + localMonk + ", " + remoteMonster
                + ", " + remoteMonk + ", " + boat + "]";
    }

    /**
     * @return the descIndex
     */
    public int getDescIndex() {
        return descIndex;
    }

    /**
     * @param descIndex
     *            the descIndex to set
     */
    public void setDescIndex(int descIndex) {
        this.descIdnex = descIdnex;
    }

    /**
     * @return the localMonster
     */
    public int getLocalMonster() {
        return localMonster;
    }

    /**
     * @param localMonster
     *            the localMonster to set
     */
    public void setLocalMonster(int localMonster) {
        this.localMonster = localMonster;
    }

    /**
     * @return the localMonk
     */
    public int getLocalMonk() {
        return localMonk;
    }

    /**
     * @param localMonk
     *            the localMonk to set
     */
    public void setLocalMonk(int localMonk) {
        this.localMonk = localMonk;
    }

    /**
     * @return the remoteMonster
     */
    public int getRemoteMonster() {
        return remoteMonster;
    }

    /**
     * @param remoteMonster
     *            the remoteMonster to set
     */
    public void setRemoteMonster(int remoteMonster) {
        this.remoteMonster = remoteMonster;
    }

    /**
     * @return the remoteMonk
     */
    public int getRemoteMonk() {
        return remoteMonk;
    }

    /**
     * @param remoteMonk
     *            the remoteMonk to set
     */
    public void setRemoteMonk(int remoteMonk) {
        this.remoteMonk = remoteMonk;
    }

    /**
     * @return the boat
     */
    public BoatLocation getBoat() {
        return boat;
    }

    /**
     * @param boat
     *            the boat to set
     */
    public void setBoat(BoatLocation boat) {
        this.boat = boat;
    }

}

 本题的状态空间就是以[3, 3, 0, 0, LOCAL]为根的一棵状态树,如果某个叶子节点表示的状态是求解状态[0, 0, 3, 3, REMOTE],则从根节点到此节点之间的直系关系节点,就是过河过程中的所有中间状态,将这些中间状态按照父子关系依次输出,就是一个求解过程。以本文开始给出的一个求解过程为例,其状态转换过程如下图所示:

  图(1)一个求解的状态转换过程

从一个状态转换到下一个状态,需要选择合适的和尚或妖怪组合完成一次过河动作,动作的概念在状态转换过程中扮演很重要的角色,如果不能明确的界定动作,随后的状态树搜索算法就无法实现。经过对本题的分析,求解算法需要10种过河动作,这10种动作分别是:

 

一个妖怪过河

两个妖怪过河

一个和尚过河

两个和尚过河

一个妖怪和一个和尚过河

一个妖怪返回

两个妖怪返回

一个和尚返回

两个和尚返回

一个妖怪和一个和尚返回


有了明确的动作的定义,最大的好处就是方便确定状态搜索过程中广度搜索的边界。算法中用ActionName标识10种动作,ActionName的java封闭如下:

public enum ActionName {
    ONE_MONSTER_GO,
    TWO_MONSTER_GO,
    ONE_MONK_GO,
    TWO_MONK_GO,
    ONE_MONSTER_ONE_MONK_GO,
    
    ONE_MONSTER_BACK,
    TWO_MONSTER_BACK,
    ONE_MONK_BACK,
    TWO_MONK_BACK,
    ONE_MONSTER_ONE_MONK_BACK,
}

状态树搜索算法

         状态树的搜索过程就是状态树的生成过程,本文介绍的算法采用的是深度优先遍历算法,每次遍历只暂时保存当前搜索的分支的所有状态,前面已经搜索过的状态是不保存的,只在必要的时候输出结果。因此,算法不需要复杂的树状数据结构保存整个状态树(也没有必要这么做),只需要一个队列能暂时存储当前搜索分支上的所有状态即可。这个队列初始时只有一个初始状态,随着搜索的进行逐步增加,当搜索算法完成后,队列中应该仍然只有一个初始状态。

        上一节已经分析过了,每个状态所能采用的过河动作只能是ActionName标识的10种动作中的一种(当然并不是每种动作都适用于此状态),有了这个动作范围,搜索状态树的穷举算法就非常简单了,只需将当前状态分别与这10种动作进行组合,就可以得到状态树上这个状态节点的若干个子节点,递归上述过程,就可以得到完整的状态树。图(2)即是深度优先搜索算法的流程图:

 

 

 图(2)状态树搜索算法流程图

算法实现主要代码

public void processState(List<ItemState> states) {
        ItemState state = states.get(states.size() - 1);
        if (state.equals(new ItemState(0, 0, 3, 3, BoatLocation.REMOTE, -1))) {
            printResult(states);
            states.remove(states.size() - 1);
            return;
        }
        ActionName actions[] = ActionName.values();
        for (int i = 0; i < actions.length; i++) {
            ItemState itemState = makeActionNewState(states, actions[i]);
            if (isValidState(states, itemState)) {
                states.add(itemState);
                processState(states);
            }
        }
        states.remove(states.size() - 1);
    }
makeActionNewState方法实现如下:

public ItemState makeActionNewState(List<ItemState> states,
            ActionName actionName) {
        ItemState state = states.get(states.size() - 1);
        ItemState newState = new ItemState();

        BoatLocation newBoatcation = this
                .getBoatLocationByActionName(actionName);
        if (newBoatcation != state.getBoat()) {
            int monster = 0, monk = 0;
            switch (actionName) {
            case ONE_MONSTER_GO:
                monster = 1;
                break;
            case TWO_MONSTER_GO:
                monster = 2;
                break;
            case ONE_MONK_GO:
                monk = 1;
                break;
            case TWO_MONK_GO:
                monk = 2;
                break;
            case ONE_MONSTER_ONE_MONK_GO:
                monster = 1;
                monk = 1;
                break;

            case ONE_MONSTER_BACK:
                monster = -1;
                break;
            case TWO_MONSTER_BACK:
                monster = -2;
                break;
            case ONE_MONK_BACK:
                monk = -1;
                break;
            case TWO_MONK_BACK:
                monk = -2;
                break;
            case ONE_MONSTER_ONE_MONK_BACK:
                monster = -1;
                monk = -1;
                break;
            }
            newState.localMonster = state.localMonster - monster;
            newState.localMonk = state.localMonk - monk;
            newState.remoteMonster = state.remoteMonster + monster;
            newState.remoteMonk = state.remoteMonk + monk;
            newState.boat = newBoatcation;
            newState.descIndex=actionName.ordinal();

            if (riverOnSideValid(newState.localMonster, newState.localMonk)) {
                if (riverOnSideValid(newState.remoteMonster,
                        newState.remoteMonk)) {
                    return newState;
                }
            }
            return null;
        }
        return null;
    }

riverOnSideValid主要是判断:妖怪是否能把和尚吃掉。代码如下:

public boolean riverOnSideValid(int monster, int monk) {
        if (monster < 0 || monk < 0) {
            return false;
        }

        if (monster == 0 || monk == 0) {
            return true;
        } else {
            if (monk >= monster) {
                return true;
            }
            return false;
        }
    }

isValidState方法主要是避免操作重复,否则会陷入死循环哦。

public boolean isValidState(List<ItemState> states, ItemState state) {
        if (state != null) {
            for (int i = 0; i < states.size(); i++) {
                ItemState itemState = states.get(i);
                if (state.equals(itemState)) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }
整个算法基本就写完了,哦,对了,还有一个很重要的问题,就是我们怎么能得到解决这个问题的操作步骤呢?那就用到我们刚开始说的descIndex字段了。

请向上看,makeActionNewState方法是不是对descIndex字段进行赋值了?那么我们就可以在printResult方法里,对descIndex进行解析输出了,最终得到我们想要的步骤。你们看看是不是很有趣呢?大家可以试试哦,这里是源码下载地址。有什么不懂可以在博客下面留言^_^^_^。顺便说一个本题共四个解如下:

Result 1
Two monster go over river  
One monster go back  
Two monster go over river  
One monster go back  
Two monk go over river  
One monster and one monk go back  
Two monk go over river  
One monster go back  
Two monster go over river  
One monster go back  
Two monster go over river  
Result 2
Two monster go over river  
One monster go back  
Two monster go over river  
One monster go back  
Two monk go over river  
One monster and one monk go back  
Two monk go over river  
One monster go back  
Two monster go over river  
One monk go back  
One monster and one monk go over river  
Result 3
One monster and one monk go over river  
One monk go back  
Two monster go over river  
One monster go back  
Two monk go over river  
One monster and one monk go back  
Two monk go over river  
One monster go back  
Two monster go over river  
One monster go back  
Two monster go over river  
Result 4
One monster and one monk go over river  
One monk go back  
Two monster go over river  
One monster go back  
Two monk go over river  
One monster and one monk go back  
Two monk go over river  
One monster go back  
Two monster go over river  
One monk go back  
One monster and one monk go over river  



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值