AIX 正方形问题》解决方案进行了改善,同时又找到了一条崭新的解决方案,没想到效果比想象中的要好,这一篇描述改良方案的算法思路及实现,下一篇介绍一个新的思路更简洁的方案。


 


为了文章的完整性,本篇仍然包括问题描述部分。


 


问题描述:


任意给定一个正方形,将正方形的各边做n等分,并将相应各点连接成水平或垂直的直线,如果从正方形的左下角(0,0)出发,沿各边线或连接线,自左向右或自下而上的方向,到达正方形的右上角(n,n),请用JAVA程序计算并输出所有可能的路径总数和具体线路.请提供相关JAVA源程序和n=2,3,4时的输出结果。输出结果按以下方式:


 


以n=1为例:


n = 1 

 

  Path1: (0,0) - (0,1) - (1,1) 

 

  Path2: (0,0) - (1,0) - (1,1) 

 

  Total = 2

 


解决思路:


上篇回顾


上一篇文章中的解决思路,为通过采用“向上优先”策略,发现通过上一条路径可以直接推导出下一条路径,在这个推导过程中,定义了两个关键点,一个命名为上一条路径的“顶极点”,一个命名为下一条路径的“突破点”,并发现这两个点之间的关系:


 


第一种:若顶极点为上边点,则按照向上优先策略,突破点和顶极点关系为:


breakPoint.x = tipPolePoint.x+1 

 

  breakPoint.y = tipPolePoint.y-1


 


第二种:若顶极点为右边点,则按照向上优先策略,突破点和顶极点关系为:


breakPoint.x = (min(x)|y=tipPolePoint.y)+1 

 

  breakPoint.y = tipPolePoint.y-1


 


思路优化


通过进一步的分析,发现以上描述的突破点和顶极点的两种关系,最核心的其实是“最后右拐点”,通过最后右拐点我们既可以简化(上一条路径)顶极点的查找,又可以简化(下一条路径)突破点的推导。


最后右拐点定义:既一条路径中最有一次向右拐弯的那个点。


 


依然按照向上优先侧略,下一条路径的突破点和上一条路径的最后右拐点,之间的关系为:


设:最后右拐点为lastRightTurnPoint,突破点为breakPoint。则有


breakPoint.x = lastRightTurnPoint.x+1 

 

  breakPoint.y = lastRightTurnPoint.y-1


 


算法特点


由于该算法统一了第一种算法中突破点推导的两种方法,简化了推导过程,所以思路更加清晰、效率更加高效。


 


程序设计:


和以上算法设计类似,这个算法的实现也包括三个类,分别简述如下:


Point:基础类,表示坐标点,和第一方案相同;


AixUtil2:工具类,按照向上优先策略,提供解决AIX正方形问题的一些静态方法,是第一方案的优化;


AixClient:一个简单的调用类,可同时测试方案一(调用AixUtil)和方案二(调用AixUtil2)。


 


源程序代码如下:


Point:基础类,表示坐标点;


package 
    qinysong.aix;

 
   /** 
   
 * <p>Title: 基础类,表示坐标点</p>
 * <p>Description: AIX 程序设计大赛---AIX正方形问题</p>
 * <p>Copyright: Copyright (c) 2006</p>
 * <p>Company: qinysong</p>
 *  
   @author 
    zhaoqingsong
 *  
   @version 
    1.0 $Date: 2006/09/05 21:44:36 $
  
   */ 
   

 
   public 
     
   class 
    Point {
   
   protected 
     
   int 
    x;
   
   protected 
     
   int 
    y;

   
   /** 
   
   * 构造函数
   *  
   @param 
    x int
   *  
   @param 
    y int
    
   */ 
   
   
   public 
    Point( 
   int 
    x,  
   int 
    y){
     
   this 
   .x  
   = 
    x;
     
   this 
   .y  
   = 
    y;
  }

   
   public 
    String toString(){
     
   return 
     
   " 
   ( 
   " 
     
   + 
    x  
   + 
     
   " 
   , 
   " 
     
   + 
    y  
   + 
     
   " 
   ) 
   " 
   ;
  }

   
   /** 
   
   * 判断是否为上边点
   *  
   @return 
    boolean
    
   */ 
   
   
   public 
     
   boolean 
    isTopBorderPoint( 
   int 
    nValue){
     
   return 
     
   this 
   .y  
   == 
    nValue;
  }

   
   /** 
   
   * 判断thePoint是否与自己相等
   *  
   @param 
    thePoint Point
   *  
   @return 
    boolean
    
   */ 
   
   
   public 
     
   boolean 
    equals(Point thePoint){
     
   return 
    ( 
   this 
   .x  
   == 
    thePoint.x)  
   && 
    ( 
   this 
   .y  
   == 
    thePoint.y);
  }

}


 


AixUtil2:工具类,按照向上优先策略,提供解决AIX正方形问题的一些静态方法,是第一方案的优化


package 
    qinysong.aix;

 
   /** 
   
 * <p>Title: 工具类,按照向上优先策略,提供解决AIX正方形问题的一些静态方法</p>
 * <p>Description: AIX 程序设计大赛---AIX正方形问题</p>
 * <p>Copyright: Copyright (c) 2006</p>
 * <p>Company: qinysong</p>
 *  
   @author 
    zhaoqingsong
 *  
   @version 
    1.0 $Date: 2006/09/05 21:52:18 $
  
   */ 
   

 
   public 
     
   class 
    AixUtil2 {

   
   private 
     
   static 
     
   int 
    nValue;
   
   private 
     
   static 
     
   int 
    laseRightTurnIndex;
   
   private 
     
   static 
    Point[] squarePoints 
   = 
     
   null 
   ;

   
   /** 
   
   * 初始化正方形边长
   *  
   @param 
    nValue int
    
   */ 
   
   
   public 
     
   static 
     
   void 
    initNValue( 
   int 
    nValue) {
     
   if 
    (nValue  
   <= 
     
   0 
   ) {
       
   throw 
     
   new 
    RuntimeException( 
   " 
   初始化正方形边长异常,长度不能小于等于零 
   " 
   );
    }
    AixUtil2.nValue  
   = 
    nValue;
    squarePoints  
   = 
     
   new 
    Point[(nValue 
   + 
   1 
   ) 
   * 
   (nValue 
   + 
   1 
   )];
     
   for 
    ( 
   int 
    y  
   = 
     
   0 
   ; y  
   <= 
    nValue; y 
   ++ 
   ){
       
   for 
    ( 
   int 
    x  
   = 
     
   0 
   ; x  
   <= 
    nValue; x 
   ++ 
   ){
        squarePoints[y 
   * 
   (nValue 
   + 
   1 
   ) 
   + 
   x]  
   = 
     
   new 
    Point(x,y);
      }
    }
  }

   
   /** 
   
   * <p>取得上一条路径的最后右拐点</p>
   * <p>最后右拐点:在到达最后顶点point(n,n)之前的最后一个向右拐的点<br>
   *
   *  
   @param 
    previousPathPoints Point[] 上一条路径节点数组
   *  
   @return 
    Point  tipPolePoint顶极点
    
   */ 
   
   
   public 
     
   static 
    Point getLastRightTurnPoint(Point[] previousPathPoints) {
    Point lastRightTurnPoint  
   = 
     
   null 
   ;
     
   int 
    index  
   = 
     
   2 
   * 
   nValue;
     
   while 
    (previousPathPoints[index].x  
   == 
    nValue
            
   || 
    previousPathPoints[index].y  
   == 
    previousPathPoints[index 
   - 
   1 
   ].y){
      index 
   -- 
   ;
    }
    laseRightTurnIndex  
   = 
    index;
    lastRightTurnPoint  
   = 
    previousPathPoints[index];
     
   return 
    lastRightTurnPoint;
  }

   
   /** 
   
   * <p>取得下一条路径的突破点,返回的突破点定义为breakPoint<br>
   * 通过几何数学分析,按照向上优先策略,有如下突破点和上一条路径的最后右拐点的关系<br>
   * breakPoint.x = lastRightTurnPoint.x+1<br>
   * breakPoint.y = lastRightTurnPoint.y-1</p>
   *
   *  
   @param 
    lastRightTurnPoint Point 最后右拐点,表示上一条路径的最后右拐点
   *  
   @return 
    Point 返回下一条路径的突破点 breakPoint
    
   */ 
   
   
   public 
     
   static 
    Point getBreakPoint(Point lastRightTurnPoint) {
     
   int 
    index  
   = 
    (lastRightTurnPoint.y  
   - 
     
   1 
   ) 
   * 
   (nValue 
   + 
   1 
   ) 
   + 
   lastRightTurnPoint.x  
   + 
     
   1 
   ;
    Point breakPoint  
   = 
    squarePoints[index];
     
   return 
    breakPoint;
  }

   
   /** 
   
   * 按照向上优先策略(即能往上走就往上走),取得下一个路径节点
   *  
   @param 
    currentPoint Point
   *  
   @return 
    Point 下一个路径节点
    
   */ 
   
   
   public 
     
   static 
    Point getNextPoint(Point currentPoint) {
     
   int 
    index  
   = 
     
   0 
   ;
     
   if 
    (currentPoint.y  
   < 
    nValue) {
      index  
   = 
    (currentPoint.y  
   + 
     
   1 
   ) 
   * 
   (nValue 
   + 
   1 
   ) 
   + 
   currentPoint.x;
    }  
   else 
     
   if 
    (currentPoint.x  
   < 
    nValue) {
      index  
   = 
    (currentPoint.y) 
   * 
   (nValue 
   + 
   1 
   ) 
   + 
   currentPoint.x  
   + 
     
   1 
   ;
    }  
   else 
    {
       
   return 
     
   null 
   ;
    }
     
   return 
    squarePoints[index];
  }

   
   /** 
   
   * 按照向上优先策略(即能往上走就往上走),取得下一条路径节点
   *  
   @param 
    previousPathPoints Point[] 上一条路径节点
   *  
   @return 
    Point[] 下一条路径
    
   */ 
   
   
   public 
     
   static 
    Point[] getNextPath(Point[] previousPathPoints) {
     
   int 
    arrayLength  
   = 
     
   2 
   * 
   nValue 
   + 
   1 
   ;
    Point lastRightTurnPoint  
   = 
    getLastRightTurnPoint(previousPathPoints);
    Point breakPoint  
   = 
    getBreakPoint(lastRightTurnPoint);
    Point[] nextPath  
   = 
     
   new 
    Point[arrayLength];
     
   int 
    index  
   = 
     
   0 
   ;
     
   for 
    (; index  
   < 
    laseRightTurnIndex; index 
   ++ 
   ) {
      nextPath[index]  
   = 
    previousPathPoints[index];
    }
    nextPath[index 
   ++ 
   ]  
   = 
    breakPoint;
    Point tempPoint  
   = 
    breakPoint;
     
   while 
    ( (tempPoint  
   = 
    getNextPoint(tempPoint))  
   != 
     
   null 
   ) {
      nextPath[index 
   ++ 
   ]  
   = 
    tempPoint;
    }
     
   return 
    nextPath;
  }

   
   /** 
   
   * 按照向上优先策略,取得第一条路径
   *  
   @return 
    Point[]
    
   */ 
   
   
   public 
     
   static 
    Point[] getFirstPath() {
    Point[] firstPath  
   = 
     
   new 
    Point[ 
   2 
   * 
   nValue 
   + 
   1 
   ];
     
   for 
    ( 
   int 
    i  
   = 
     
   0 
   ; i  
   <= 
    nValue; i 
   ++ 
   ) {
      firstPath[i]  
   = 
    squarePoints[i 
   * 
   (nValue 
   + 
   1 
   )];
    }
     
   for 
    ( 
   int 
    i  
   = 
     
   1 
   ; i  
   <= 
    nValue; i 
   ++ 
   ) {
      firstPath[nValue  
   + 
    i]  
   = 
    squarePoints[nValue 
   * 
   (nValue 
   + 
   1 
   ) 
   + 
   i];
    }
     
   return 
    firstPath;
  }

   
   /** 
   
   * <p>按照向上优先策略(即能往上走就往上走),取得下一条路径节点<br>
   * 这个函数是上面getNextPath和getFirstPath的合并,用以得到整体的下一条路径<br>
   * 如果previousPathPoints 为空,则取得第一条路径<br>
   * 如果previousPathPoints不为空,则根据其取得下一条路径</p>
   *  
   @param 
    previousPathPoints Point[] 上一条路径节点
   *  
   @return 
    Point[] 下一条路径
    
   */ 
   
   
   public 
     
   static 
    Point[] getTotalNextPath(Point[] previousPathPoints) {
     
   if 
    (previousPathPoints  
   == 
     
   null 
   ){
       
   return 
    getFirstPath();
    }  
   else 
    {
       
   return 
    getNextPath(previousPathPoints);
    }
  }

   
   /** 
   
   * 判断是否是最后一条路径
   *  
   @param 
    pathPoints Point[]
   *  
   @return 
    boolean
    
   */ 
   
   
   public 
     
   static 
     
   boolean 
    isLastPath(Point[] pathPoints){
     
   int 
    middleIndex  
   = 
    nValue;
     
   return 
    pathPoints[middleIndex].y  
   == 
     
   0 
   ;
  }

   
   /** 
   
   * 按照题目要求格式打印一条路径的节点
   *  
   @param 
    pathNumber int
   *  
   @param 
    pathPoints Point[]
    
   */ 
   
   
   public 
     
   static 
     
   void 
    printlnPathPoints( 
   int 
    pathNumber, Point[] pathPoints) {
    StringBuffer pathStringBuffer  
   = 
     
   new 
    StringBuffer();
    pathStringBuffer.append( 
   " 
   Path 
   " 
     
   + 
    pathNumber  
   + 
     
   " 
   : 
   " 
   );
     
   int 
    arrayLength  
   = 
     
   2 
   * 
   nValue 
   + 
   1 
   ;
     
   for 
    ( 
   int 
    i  
   = 
     
   0 
   ; i  
   < 
    arrayLength; i 
   ++ 
   ) {
      pathStringBuffer.append(pathPoints[i].toString()  
   + 
     
   " 
   - 
   " 
   );
    }
    String pathString  
   = 
    pathStringBuffer.toString();
     
   if 
    (pathString.length() 
   > 
   0 
   ) pathString  
   = 
    pathString.substring( 
   0 
   ,pathString.length() 
   - 
   1 
   );
    System.out.println(pathString);
  }

}


 


AixClient:一个简单的调用类,可同时测试方案一(调用AixUtil)和方案二(调用AixUtil2)


package 
    qinysong.aix;

 
   import 
    java.util.Date;

 
   /** 
   
 * <p>Title: 调用类,该类通过工具类AixUtil提供的方法,遍历一个正方形的路径</p>
 * <p>Description: AIX 程序设计大赛---AIX正方形问题</p>
 * <p>Copyright: Copyright (c) 2006</p>
 * <p>Company: qinysong</p>
 *  
   @author 
    zhaoqingsong
 *  
   @version 
    1.0 $Date: 2006/09/05 22:49:22 $
  
   */ 
   

 
   public 
     
   class 
    AixClient {

   
   public 
     
   static 
     
   void 
    main(String[] args) {
    System.out.println( 
   " 
   AixClient.main begin ...... 
   " 
   );
    System.out.println( 
   " 
   AixClient.main 方案1 
   " 
   );
    Date beginTime1  
   = 
     
   new 
    Date();
    System.out.println( 
   " 
   beginTime1: 
   " 
     
   + 
    beginTime1.toString());
     
   int 
    nValue  
   = 
     
   5 
   ;
    Point[] pathPoints  
   = 
     
   null 
   ;
     
   int 
    pathNumber  
   = 
     
   0 
   ;
    System.out.println( 
   " 
   当n= 
   " 
     
   + 
    nValue);
    AixUtil.initNValue(nValue);
     
   do 
    {
      pathPoints  
   = 
    AixUtil.getTotalNextPath(pathPoints);
       
   ++ 
   pathNumber;
      AixUtil.printlnPathPoints(pathNumber, pathPoints);
    } 
   while 
    ( 
   ! 
   AixUtil.isLastPath(pathPoints));
    System.out.println( 
   " 
   Total: 
   " 
     
   + 
    pathNumber);
    Date endTime1  
   = 
     
   new 
    Date();
    System.out.println( 
   " 
   end endTime1 :   
   " 
     
   + 
    endTime1.toString());


    System.out.println( 
   " 
   AixClient.main 方案2 
   " 
   );
    Date beginTime2  
   = 
     
   new 
    Date();
    System.out.println( 
   " 
   beginTime2: 
   " 
     
   + 
    beginTime2.toString());
    System.out.println( 
   " 
   当n= 
   " 
     
   + 
    nValue);
    AixUtil2.initNValue(nValue);
    pathPoints  
   = 
     
   null 
   ;
    pathNumber  
   = 
     
   0 
   ;
     
   do 
    {
      pathPoints  
   = 
    AixUtil2.getTotalNextPath(pathPoints);
       
   ++ 
   pathNumber;
      AixUtil2.printlnPathPoints(pathNumber, pathPoints);
    } 
   while 
    ( 
   ! 
   AixUtil2.isLastPath(pathPoints));
    System.out.println( 
   " 
   Total: 
   " 
     
   + 
    pathNumber);
    Date endTime2  
   = 
     
   new 
    Date();
    System.out.println( 
   " 
   end endTime2 :   
   " 
     
   + 
    endTime2.toString());
    System.out.println( 
   " 
   方案1所花时间毫秒:   
   " 
     
   + 
    (endTime1.getTime()  
   - 
    beginTime1.getTime()));
    System.out.println( 
   " 
   方案2所花时间毫秒:   
   " 
     
   + 
    (endTime2.getTime()  
   - 
    beginTime2.getTime()));

    System.out.println( 
   " 
   AixClient.main end   ...... 
   " 
   );
  }
}