• 穷举算法解谜初探


    之前无意中看到推箱子游戏,想了一下这个游戏的求解算法,感觉非常复杂,苦思无果。最近在看《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 #
     # # # # # #
    
    

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

  • 相关阅读:
    对每项物品,找出最贵价格的物品的经销商
    每项物品的的最高价格是多少?
    找出最贵物品的编号、销售商和价格
    查数据库有哪些表、查数据库
    取某字段不为空的数据is not null
    mysql匹配模式
    找出包含正好5个字符的名字
    要想找出正好包含5个字符的名字
    要想找出包含“w”的名字
    要想找出以“y”结尾的名字
  • 原文地址:https://www.cnblogs.com/wangnfhy/p/4957171.html
Copyright © 2020-2023  润新知