今天继续看了状态树搜索算法,问题是三个和尚三个妖怪过河,无论是过河前还是过河后,和尚的数目都不能小于妖怪的数目,不然就会被吃掉。
题目的初始条件是三个和尚和妖怪都在河的一边,然后每次只能够坐上两个上船,然后到达另一边,其中一个再坐船回去。
其实这个就跟我前几天做的那个状态树搜索算法,三个水桶平分水问题一样,都是搜索状态树,解决这个问题需要找到一条从初始状态变换到终止状态的路径,使用穷举法,遍历所有由妖怪、和尚和小船位置构成的空间状态,寻找从初始状态到最终状态的变换路径。
建立数学模型,描述问题的数据结构,问题的状态模型不仅要描述静止状态,也要描述转换动作,因为这对算法设计至关重要。
状态的数学模型与状态树,三个和尚与三个妖怪和一个小船,7个属性,但是有效的空间状态其实只有5个,过河前的和尚妖怪数目,小船位置,过河后的妖怪和尚数目。
所以我们只要记录这五个属性就可以了,我用的是字符串备忘录,前面4个字符记录过河前后的和尚妖怪数目,最后几个英文是小船的状态,然后比较状态就是字符串的匹配问题了。
作者用C++做,他用到了结构体和枚举变量,
在java中有个枚举类,所以我是用枚举类来实现的。
搜索算法,状态树的遍历,状态树的遍历里面暗含了状态树生产的过程,促使上一个状态向下一个状态转换驱动。
动作模型里面定义了10个动作,10个动作是这个问题游戏中所有可能出现的过河动作,但是并不是所有的动作状态都符合问题要求,所以必须加上判断条件。
剪枝和重复状态判断:
判断一个状态是否适用于当前状态,要有判断合法的算法。
还要有判断是否重复,避免出现死循环,否则,算法可能无法跳出当前状态,java中会出现栈溢出,所以要实现剪枝操作,让深度遍历可以快速收敛。
最后结果是由4种过河方案。
我的java代码如下:
主类用于启动算法实现类
package 状态树搜索算法再现妖怪和尚过河; public class Main { public static void main(String[] args) { Main ma = new Main(); ma.initMain(); } public void initMain(){ GoToRiver gv = new GoToRiver(); int[] str = {3,3,0,0}; StateNode root = new StateNode(str); gv.SearchState(root); } }
状态节点类,用于存放空间状态,分析判断空间状态是否合法
package 状态树搜索算法再现妖怪和尚过河; public class StateNode { public int[] NumStr = new int[4]; //一维数组存放当前和尚妖怪的数量,0和2表示和尚,1和3表示妖怪 public String boatState = "LOCAL";//用字符串表示当前船的位置 河对岸是REMOTE public StateNode back ; public StateNode(int[] str){ NumStr[0] = str[0]; NumStr[1] = str[1]; NumStr[2] = str[2]; NumStr[3] = str[3]; } public boolean IsFinalState(StateNode current){//查看节点是否是最终状态 if( current.getText(current).equals("0033REMOTE") ){//如果是最终状态 return true; } return false; } /** * 判断过河是否合法 * * @return */ public boolean CanTakeAction(StateNode node,AcrossRiverMethod as){//和尚要大于妖怪 if( node.boatState == as.boatstate ){//当前的船和移动后的船在同一边就返还false return false; } if( node.NumStr[1] + as.monsterAction < 0 || node.NumStr[1] +as.monsterAction > 3 ){//移动后的妖怪数目小于0大于3的话,返回false return false; } if( node.NumStr[0] + as.monkAction < 0 || node.NumStr[0] + as.monkAction > 3 ){//移动后的和尚数目小于0大于3的话,返回false return false; } return true; } public String getText(StateNode node){//备忘录 String s = Integer.toString(node.NumStr[0]) + Integer.toString(node.NumStr[1]) +Integer.toString(node.NumStr[2]) + Integer.toString(node.NumStr[3]) + node.boatState; return s; } public boolean MakeActionNewState(StateNode current,StateNode next, AcrossRiverMethod acrossRiverMethod){//传入一个过河的状态 if( current.CanTakeAction(current,acrossRiverMethod) ){//判断是否能够过河,能够过河就执行过河操作 next.NumStr[0] = current.NumStr[0] + acrossRiverMethod.monkAction;//下一个状态为当前状态加上操作后的状态 next.NumStr[2] = 3 - next.NumStr[0]; next.NumStr[1] = current.NumStr[1] + acrossRiverMethod.monsterAction; next.NumStr[3] = 3 - next.NumStr[1]; next.boatState = acrossRiverMethod.boatstate; return true; } return false; } public boolean IsValidState(StateNode node){ if( node.NumStr[0] > 0 && node.NumStr[1] > node.NumStr[0] ){ return false; } if( node.NumStr[2] > 0 && node.NumStr[3] > node.NumStr[2] ){ return false; } return true; } }
package 状态树搜索算法再现妖怪和尚过河; public class GoToRiver { public void SearchState(StateNode node){ if( node.IsFinalState(node) ){ Tool.PrintResult(node); System.out.println("succeed to across the river!"); return ; } for(int i=0;i<AcrossRiverMethod.values().length;i++){ SearchStateOnNewAction(node,AcrossRiverMethod.values()[i]); } } public void SearchStateOnNewAction(StateNode node,AcrossRiverMethod acrossRiverMethod){ StateNode next = new StateNode(node.NumStr); boolean DnewState = next.MakeActionNewState(node, next, acrossRiverMethod); if( DnewState ){ next.back = node; if( next.IsValidState(next) && !Tool.ISProcessedState(next) ){ SearchState(next); } } } }工具类,用于查找备忘录帮助分析判断状态是否合法。
在有符合状态的时候,输出状态过程
package 状态树搜索算法再现妖怪和尚过河; import java.util.Vector; public class Tool { /** * 如果state已经存在,返回true * @param state * @return */ public static boolean ISProcessedState(StateNode state){ StateNode current = state;//current为当前状态的对象 while( state.back != null ){//back为上一对象 if( current.getText(current).equals(state.back.getText(state.back)) ){//如果back存在就判断当前状态是否存在,存在返回true return true; } state = state.back;//逐层查找 } return false; } public static void PrintResult(StateNode node){ System.out.println(node.getText(node)); while( node.back != null ){ System.out.println(node.back.getText(node.back)); node = node.back; } } }
package 状态树搜索算法再现妖怪和尚过河; public enum AcrossRiverMethod{ ONE_MONSTER_GO("REMOTE",-1,0),// -1代表妖怪改变的数量 0代表和尚改变的数量 TWO_MONSTER_GO("REMOTE",-2,0),// 代表左边岸上的变动情况 ONE_MONK_GO("REMOTE",0,-1), TWO_MONK_GO("REMOTE",0,-2) ,ONE_MONSTER_ONE_MONK_GO("REMOTE",-1,-1), ONE_MONSTER_BAKC("LOCAL",1,0), TWO_MONSTER_BACK("LOCAL",2,0), ONE_MONK_BACK("LOCAL",0,1), TWO_MONK_BACK("LOCAL",0,2), ONE_MONSTER_ONE_MONK_BACK("LOCAL",1,1); public String boatstate; public int monsterAction,monkAction; AcrossRiverMethod(String boatstate,int monsterAction,int monkAction){ this.boatstate = boatstate; this.monsterAction = monsterAction; this.monkAction = monkAction; } }下面是我的测试图,答案却是是4种没错:
作者博客:http://blog.csdn.net/orbit/article/details/6630461