传教士与野人过河问题 —— 人工智能实验算法

问题描述

  有 N 个传教士和 N 个野人来到河边渡河,河岸有一条船,每次至多可供 k 人乘渡。问:传教士为了安全起见,应如何规划摆渡方案,使得任什么时候刻, 河两岸以及船上的野人数目老是不超过传教士的数目(不然不安全,传教士有可能被野人吃掉)。 即求解传教士和野人从左岸所有摆渡到右岸的过程当中,任什么时候刻知足 M (传教土数) ≥ C 野人数)和 M+C≤k 的摆渡方案。git

 写在前面

传教士与野人过河问题是人工智能里面很是经典的算法题,曾经是2012年360公司的面试题,所以网上有各类各样的解决思路和代码设计,可是我发现网上的算法思路很是好,代码设计的尽管多种多样,但都不知足咱们今天这个项目需求,大部分都是规定好了3个传教士和3个野人,船上最多只能作2人。这样的限制是不符合今天这种需求的,由于今天的问题描述中并无说明会有几个传教士和野人,只能保证传教士和野人通常多,也没有说明一条船最多作几我的,所以网上大部分代码案例不合题。再有一种就是容许咱们输入有多少人和船最多坐多少人,可是他没有找出全部的方案。所以,今天就本身动手作一下这个算法。github

算法思路

  这个算法很经典,网上有不少的解题思路,我也看了不少,有一个感受讲的很是不错的推荐一下,就是 CSDN-魏宇轩 前辈的博文,讲解的很是棒,思路很清晰,并且他还使用 C语言写了出来,可是我运行出了问题,一直没有搞通,因而本身用 C# 写了一个,其中算法设计思路是按照 魏宇轩 前辈的思路走的。面试

算法分析

 参考各3人渡河,船最多载2人的算法思路。最总项目人数自定义。算法

  初始状态:左岸,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,以及其余表示方法。为何这么说?由于任何数乘以一,其自己都不会改变(有我也不会认可)。而正负号在此起到关键做用,咱们使用正负一去乘以五种渡河方案的改变数值,从而获得的就是咱们变换的正确结果,不论左岸右岸,都是正确合理的。多线程

项目关键代码

主要用来判断这条方案是否可行。函数

        // 是否重复操做
            for (int i = 0; i < index; i++) { if (m.left_c == m_fun[i].left_c && m.left_y == m_fun[i].left_y) { if (m.boat_location == m_fun[i].boat_location) { return 0; } } } // 人数是否合理
            if (m.left_c < 0 || m.left_y < 0 || m.right_c < 0 || m.right_y < 0) { return 0; } // 传教士的人数是否大于等于野人
            if ((m.left_c < m.left_y && m.left_c != 0) || (m.right_c < m.right_y && m.right_c != 0)) { return 0; }

递归算法,主要是用来计算船的渡河载客可能性。人工智能

// 递归算法
            for (int cchuan = chuan; cchuan >= 0; cchuan-- ) { for (int ychuan = chuan; ychuan >= 0 ; ychuan--) { if ((cchuan >= ychuan && cchuan + ychuan <= chuan && cchuan + ychuan>0) || (cchuan < ychuan && cchuan == 0)) { mm.left_c = m.left_c - cchuan * m.boat_location; mm.left_y = m.left_y - ychuan * m.boat_location; mm.right_c = m.right_c + cchuan * m.boat_location; mm.right_y = m.right_y + ychuan * m.boat_location; mm.boat_location = (-m.boat_location); index = index + 1; m_fun.Insert(index, mm); mCalculation(m_fun[index]); index = index - 1; } } }

 

最终效果

界面打开,默认传教士和野人的渡河人数都是3人,船的最大载客量为2人,能够本身修改。spa

  

设置完人数后,点击“开始计算渡河方案”按钮,开始计算全部可行方案。

   

【问题】

  根据设置人数和最大载人量的不一样,算法可执行方案大不一样,由于代码中并无使用多线程,在计算过程之中会存在运算数据过大,计算时间过长,在计算的过程中出现程序界面卡死,直到利用足够的时间计算完成为止。

  资料参考自:CSDN-魏宇轩:http://www.javashuo.com/article/p-bigzpkcc-dq.html

项目代码

    https://github.com/wjw1014/CrossingtheRiver (仅供参考)

相关文章
相关标签/搜索