Java编程能力强化(2)——搜索解决方案类问题的通用解法

前几天发了一篇文章《Java编程能力强化——狼羊过河问题》,有朋友指出了一些问题,这些问题有:1、没有采用面向对象的思想,没有定义自己的类,好像与Java无关,像是C语言的编程思维。2、没有给出代码的思路。3、对是否能够提高Java编程能力表示怀疑。本文首先对第一个问题进行解释,然后给出这一类问题的通用的解决方案,然后对之前的狼羊过河代码进行分析,主要是对涉及的Java知识进行分析。

 


 

第一,编程序就是解决问题,解决问题才是硬道理

简单理解,程序就是对用户的输入进行处理,然后输出处理的结果。把人的需求表示成计算机可以理解的形式,然后由计算机进行处理,然后把计算的结果再转换为用户可以理解的形式。信息的表示是数据库结构的问题,信息的处理是算法要解决的问题,所以在计算机课程中数据结构和算法是非常重要的两门课。而数据结构和算法的实现需要一门语言,所以你可能会看到数据结构(C语言)或者数据结构(Java语言),算法通常采用伪代码。

对于狼羊过河问题,如果采用面向对象的分析,需要考虑描述中出现的对象,根据这些对象抽象出来类以及类层次,这里涉及的对象包括3只狼、3只羊、一条船,以及狼羊共存的规则以及船的容量,但是这些对象基本上没有属性和行为,所以构造成类也并不合适,所在在给出的参考代码中没有进行面向对象的设计。

另外这个题目主要是考察如何解决问题,关心的是羊和狼的数量,如何在各种约束条件下把狼和羊运送到河对岸,所以参考答案的重点是算法。

而在实现算法的过程用到了String、StringBuffer、Integer、异常处理、数组、方法的递归调用等Java语言特性,所以不能说与Java无关。

个人觉得Java中需要掌握的有3个方面的知识:基本语法、常用类库和面向对象特性,而本练习题主要是对前两个方面的强化。

 


 

第二,搜索解决方案类问题的通用解法

这类问题有下面的特点:

1、有初始状态,例如3只狼和3只羊需要过河,意味着刚开始3只狼和3只羊都在河的一个岸边。

2、有目标状态,例如3只狼和3只羊要到达河的对岸,意味着最后3只狼和3只羊都要和的对岸。

3、通过一些规则可以使状态发生变化,例如可以用船把2只狼运送到和的对岸,这时候的状态就是河的一边有3只羊和1只狼,河的另一边有2只浪(当然可以根据一边的数字推算出另一边的数字,因为要么在这边要么在那边)。

 

人是如何来解决这类问题的呢?看下面的图:

 

首先考虑3只狼3只羊在左边,船只能装两只动物,所以有5种方案,然后看看每一种方案产生的结果是不是想要的结果,如果不是,继续看有哪些情况,上面的图展示了这个过程。图中的右表示把动物运送到对岸,左表示船再回来。

 

程序如何来实现这个过程呢?

程序的实现过程是对人思考问题的模拟。有两种方式,一种方式按照层来找,初始状态下,考虑5种可能,然后对5种可能进行分析,然后对每一种情况的可能变化情况再考虑,直到找到目标状态,或者不能再变化,称为广度优先搜索。另一种方式是先考虑5种情况中的一种,考虑完之后考虑这一种情况能够转移成哪些状态,然后再考虑其中一个,称为深度优先搜索。之前给出的参考答案采用的是深度优先搜索。

例如下图,如果按照广度优先:则每个状态处理的顺序为:1(第一层) 2 3(第二层) 4 5 6(第三层) 7 8 9 10(第四层)

如果按照深度优先:则每个状态的处理顺序为:1 2 4 7 3 5 8 9 6 10,2下面的情况处理完再处理3下面的情况,同样5下面的情况处理完才处理6下面的情况。

 

算法的关键部分如下:

1、需要定义1个结构来表示状态,包括初始状态、中间状态和目标状态。我的参考代码中使用了int state[]表示状态。

2、要明确初始状态和目标状态。代码中初始状态为[3,3]表示岸上有3只狼和3只羊,目标状态为[0,0]表示狼和羊都过河了。

3、要明确状态转换的规则,通常每个规则可以定义为方法,方法的输入是一种状态,而输出是另一种状态。代码中move方法完成了这个功能,只是我把多个规则写在了同一个方法中。

4、编写主程序,从初始状态开始,不断地进行状态转换直到结束。有两种处理方式,方法的递归调用,和循环。

 

可能存在的问题:

1、死循环,最直观的例子,让一只狼到对岸,然后再回来,然后再过去,然后再回来...,永远不会有结束的时候,对于这个问题需要进行控制,有效的方式是记录走过的状态,如果发现重复,放弃这个方案。程序中有两个地方进行了控制。

2、解的空间可能会非常大,如果不采取措施可能永远也运行不完。例如下象棋,大家都知道谁想的远,谁就可能赢,如果让电脑把100步之后的情况都考虑到,我想人可能很难赢,但是让电脑考虑100步需要太大的计算量,如果考虑每一步有20种走法,则需要考虑20的100次方,可以想象这个数字有多大。对于这个问题,通常会采用启发式搜索以及剪枝等。感兴趣的同学可以参考相关书籍。


 

三、关键代码分析

完整代码参考:Java编程能力强化——狼羊过河问题

关键点分析:

1、public void next(int state[],StringBuffer str)

递归调用的方法,处理指定的状态下面的所有状态。

2、  if(str==null){ //表示第一步
   // 一只狼一只羊
   newState = move(state,"-1-1");
   next(newState,new StringBuffer("-1-1"));
   // 两只狼过河
   newState = move(state,"-2-0");
   next(newState,new StringBuffer("-2-0"));
   return;
  }
对初始状态进行处理,也可以合并在下面的代码中,如果合并需要判断str是否为空。另外,这里的return不能省略。

3、  if(state[0]==0 && state[1]==0){ // 全部转移到右岸了
   printResult(str);
   return;
  }
表示得到目标状态,处理结束。

4、   // 两只狼
   if(state[0]>=2 && !str.substring(0,4).equals("+2+0")){
    newState = move(state,"-2-0");
    if(check(newState)){
     next(newState,new StringBuffer(str).insert(0,"-2-0"));
    }
   }
注意:new StringBuffer(str).insert(0,"-2-0")不能写成
str.insert(0,"-2-0")

另外!str.substring(0,4).equals("+2+0")的作用防止去和回船上的动物是一样的。

5、 public int[] move(int state[],String info){
  int lang = 0;
  try{
   lang = Integer.parseInt(info.substring(0,2));
  }catch(Exception e){
   lang = Integer.parseInt(info.substring(1,2));
  }
  int yang = 0;
  try{
   yang= Integer.parseInt(info.substring(2));
  }catch(Exception e){
   yang = Integer.parseInt(info.substring(3));
  }
  int[] result = new int[state.length];
  result[0] = state[0]+lang;
  result[1] = state[1]+yang;
  return result;
 }

注意:try语句的使用,因为+3转换为数字的时候出现异常

另外不能直接修改state的元素值然后把state返回。

6、public boolean check(int state[]){
判定羊的个数不能小于狼的个数。

7、public boolean hasExist(StringBuffer str){
判断是否有死循环。

8、public void printResult(StringBuffer str){
输出结果方案。

大家可以试着使用广度优先的方式实现代码,可以不用递归的方式进行处理(如果递归的层数比较多,会出现堆栈溢出问题)。

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页