穷举算法解谜初探

之前无意中看到推箱子游戏,想了一下这个游戏的求解算法,感觉非常复杂,苦思无果。最近在看《java并发编程实践》时得到启发,可以利用穷举算法来解决这一问题,而且很多类似的问题都可以用穷举算法解决。

这里定义三个类:

PuzzleState:定义解谜过程中的一个状态或者说一个步骤。
SolvePuzzle:解谜线程管理类
Solving:解谜线程类

一个接口:

PuzzleModeling:问题建模,在这里定义问题规则。
PuzleState
/**
 * 解谜过程中的一个状态或者一个步骤,与问题建模有关。在推箱子游戏中,是工人的一步移动;<br>
 * 每个状态都维护一个父状态和一个关闭状态列表。父状态用于解决方法的回溯,从目标状态返回起点状态;
 * 关闭列表保存了到达当前状态前已经处理过的状态。<br>
 * 关闭列表在每个状态中作为内部变量维护,保证即使父状态和子状态由不同线程处理,其结果都是一致的。<br>
 * @author nfhy
 * 2015年9月29日
 * <br> 
   myBlog: http://nfhy.wang
 */
public abstract class PuzzleState {
  
  /**每个状态的唯一ID.*/
  protected String uniqueId;
  
  /**指向父状态,计算解决方法价值,回溯解决方法时使用.*/
  protected PuzzleState parent;
  
  /**关闭状态列表,保存状态id,这一列表中的状态不需要再次处理.*/
  private ArrayList<String> closeList;
  
  /**生成状态唯一ID,与问题建模有关.*/
  public abstract void generateUniqueId();
  
  public String getUniqueId() {
    return this.uniqueId;
  }
  
  public void setParent(PuzzleState parent) {
    this.parent = parent;
  }
  
  /**当前状态的后续状态,一般情况下,如果后续状态出现在closeList中,这一状态可以跳过不处理.*/
  public abstract <K extends PuzzleState> List<K> next();
  
  /**从当前状态回溯到起始状态,返回一个状态队列.*/
  public abstract List<PuzzleState> traceBackToStart();
  
  /**计算从当前状态回溯到起始状态的步数,当谜题有多个解时,可以通过这一方法找到最优解.*/
  public abstract int traceBackCost();

  /**在console中打印状态简图的方法.*/
  public void draw(){}
  
}

SolvePuzzle

/**
 * 解谜线程管理类。维护一个线程池 es;
 * 维护一个开启状态阻塞队列 openList,队列中的状态都是待处理的,所有线程都可以在这里取得任务;<br>
 * 保存一个或多个目标状态 targetStates;维护一个解决方法阻塞队列 findStates,
 * 每找到与目标状态相同的状态,就保存在队列中;<br>
 * 保存一个开始状态 start,所有后续状态的顶层父状态都是开始状态;
 * 保存一个闭锁 latch,当所有线程执行完毕或异常退出时,latch-1<br>
 * @author nfhy
 * 2015年9月29日
 * <br> 
   myBlog: http://nfhy.wang
 */
public class SolvePuzzle {
  
  /**
   * 等待线程执行的最长等待时间.
   */
  private static final int maxWaitLength = 10;
  /**
   * 解密线程的线程池.
   */
  final ExecutorService es = 
      Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
  
  /**开启状态阻塞队列,保存待处理的状态,线程以阻塞方式从队列中取值,阻塞时间超过规定时间,表明没有新的状态产生,线程结束.*/
  final LinkedBlockingQueue<PuzzleState> openList = new LinkedBlockingQueue<PuzzleState>();
  
  /**解决方法阻塞队列,保存找到的解决方法,也就是和目标状态相同的状态.*/
  final LinkedBlockingQueue<PuzzleState> findStates = new LinkedBlockingQueue<PuzzleState>();
  
  /**一个或多个目标状态,有的谜题可能有多个解.*/
  PuzzleState[] targetStates;
  
  /**谜题初始状态.*/
  PuzzleState start;
  
  /**线程执行闭锁,只有所有线程执行完毕后,才开是梳理解决方法.*/
  final CountDownLatch latch = new CountDownLatch(Runtime.getRuntime().availableProcessors());
  
  /**构造方法,提供目标状态和初始状态.
   * @param targetStates 目标状态
   * @param start 初始状态
   */
  public SolvePuzzle(PuzzleState[] targetStates, PuzzleState start) {
    this.targetStates = targetStates;
    this.start = start;
  }
  
  /**解谜开始方法.*/
  public void start() {
    try {
      openList.put(start);
    } catch (InterruptedException e) {
      System.out.println("start interrupted, abort");
      return;
    }
    for (int i = 0; i < Runtime.getRuntime().availableProcessors(); ++i) {
      es.submit(new Solving(targetStates));
    }
    try {
      latch.await();
    } catch (InterruptedException e) {
      System.out.println("latch waiting interrupted");
    }
    shutdown();
    handleResult();
  }
  
  /**遍历解决方法,找到最优解.*/
  private void handleResult() {
    PuzzleState result;
    PuzzleState minCostState = null;
    while ((result = findStates.poll()) != null) {
      System.out.println("---- " + result.traceBackCost());
      if (null == minCostState || minCostState.traceBackCost() > result.traceBackCost()) {
        minCostState = result;
      }
    }
    minCostState.draw();
    List<PuzzleState> traces = minCostState.traceBackToStart();
    Collections.reverse(traces);
    for (PuzzleState state : traces) {
      state.draw();
    }  
  }
  
  /**等待线程池关闭,如果100秒仍没有关闭,手动关闭.*/
  public void shutdown() {
    if (!es.isTerminated()) {
      try {
        es.awaitTermination(maxWaitLength, TimeUnit.SECONDS);
      } catch (InterruptedException e) {
        System.out.println("wait for termination interrupted");
      } finally {
        es.shutdown();
      }
    }
  }

Solving

/**
   * @Title SolvePuzzle.java
   * @author nfhy
   * @date 2015年9月29日
   * @Description 解谜线程类,定义了解谜的过程:<br>
   *     1.从开启队列中取得待处理状态,如果超过等待时间仍没有取到新的状态,跳转步骤5<br>
   *     2.获取该状态的后续状态<br>
   *     3.如果不存在后续状态,返回步骤1;
   *     4.如果存在后续状态,遍历这些状态,如果这些状态与目标状态相同,记录在解决方法队列中,
   *     如果这些状态不在开启列表中,将它们放入开启列表,返回步骤1。<br>
   *     5.结束线程
   * <br> 
     myBlog: http://nfhy.wang
   */
  class Solving implements Callable<Boolean> {
    
    PuzzleState nowState;
    
    PuzzleState[] targetStates;
    
    public Solving(PuzzleState[] targetStates) {
      this.targetStates = targetStates;
    }
    
    private boolean checkTarget(PuzzleState state) {
      for (PuzzleState target : targetStates) {
        if (null != target && target.getUniqueId().equals(state.getUniqueId())) {
          return true;
        }
      }
      return false;
    }
    
    private boolean getState() throws Exception {
      while ((nowState = openList.poll(maxWaitLength, TimeUnit.SECONDS)) != null) {
        List<PuzzleState> nextStates = nowState.next();
        for (PuzzleState state : nextStates) {
          if (checkTarget(state)) {
            findStates.add(state);
          }
          if (!openList.contains(state)) {
            openList.put(state);
          }
        }
      }
      return true;
    }

    @Override
    public Boolean call() {
      try {
        getState();
      } catch (Exception e) {
        e.printStackTrace();
      } finally {
        latch.countDown();
      }
      return true;
    }
    
  }

为了解决推箱子问题,继承SingleState,实现PuzzleModeling,代码不在这贴了,有兴趣的可以去我的github下载

测试场景非常简单,5*4的地图,四周围墙,两个箱子,两个仓库。W表示工人,#表示墙壁,*表示仓库,B带表箱子。

初始状态:
 # # # # # #
 # * * B S #
 # W B # * #
 # * * * S #
 # # # # # #
目标状态:

 # # # # # #
 # * * * B #
 # * * # * #
 # W * * B #
 # # # # # #
 
 最优结果回溯(从初始状态到最终状态,最快需要11步):
 注意,这可能不是唯一的最优解,但肯定是最优解之一。
 # # # # # #
 # * * * B #
 # * * # * #
 # W * * B #
 # # # # # #

 # # # # # #
 # * * B S #
 # W B # * #
 # * * * S #
 # # # # # #

 # # # # # #
 # W * B S #
 # * B # * #
 # * * * S #
 # # # # # #

 # # # # # #
 # * W B S #
 # * B # * #
 # * * * S #
 # # # # # #

 # # # # # #
 # * * W B #
 # * B # * #
 # * * * S #
 # # # # # #

 # # # # # #
 # * W * B #
 # * B # * #
 # * * * S #
 # # # # # #

 # # # # # #
 # * * * B #
 # * W # * #
 # * B * S #
 # # # # # #

 # # # # # #
 # * * * B #
 # W * # * #
 # * B * S #
 # # # # # #

 # # # # # #
 # * * * B #
 # * * # * #
 # W B * S #
 # # # # # #

 # # # # # #
 # * * * B #
 # * * # * #
 # * W B S #
 # # # # # #

 # # # # # #
 # * * * B #
 # * * # * #
 # * * W B #
 # # # # # #

 # # # # # #
 # * * * B #
 # * * # * #
 # * W * B #
 # # # # # #

 # # # # # #
 # * * * B #
 # * * # * #
 # W * * B #
 # # # # # #

版权声明:本文为博主原创文章,未经博主允许不得转载.

posted @ 2015-11-11 19:23  农夫与花园  阅读(289)  评论(0编辑  收藏  举报