卒的移动问题(JAVA)

来源:互联网 发布:Mac电脑打魔兽世界卡吗 编辑:程序博客网 时间:2024/04/30 17:47

一、问题描述:

1、在部分的象棋棋盘(都是方格,大小可从键盘输入)中,假设卒只能向下或者向右移动,且卒在原点A(0,0)位置,求卒移动到棋盘最大的终点位置B(m,n)的所有路径数;

2、约定B点不同于A点;


二、思路:

1、通过排列组合方法解题;

2、通过面向对象构造模型解;


我选择使用Java语言实现第二种。

1、第一次直接使用迭代实现,发现效率极其低下,在棋盘较大时,花费时间特别长;

2、发现可以通过"经过每个点的路径数等于其所有下一点的路径数之和"这个关系解:

具体思路如下:

递归计算卒的下一步位置的经过次数
1、第一次取(0,0)点的次数;
2、经过(0,0)点的次数也等于经过(0,1)、(1,0)的次数之和;
3、经过(0,1)点的次数等于经过(1,1)、(0,2)的次数之和,同理(1,0)=(1,1)+(2,0)
……
经过如上可以观察得出结论:
经过当前点X的次数=X所有下一点的次数之和,且所有下一点的次数和X的次数相同

三、算法:
1、保存经过(0,0)点的次数为1,并保存至集合M中;
2、遍历集合M中的所有点,假设当前点为X,遍历X的所有合法的下一点(不合法直接丢弃),
1)若下一点N不在M中,则保存N至M中,其次数为X的次数;
2)若N已经在集合M中,则N的次数为N的次数与X的次数之和;
3)遍历完X的所有子节点后,从M中删除X节点;
3、重复2,直至结合中为空或者只有终点


用图最容易说明白:

1、相当于每次都是取对角线上的点的次数,如第一次取的是(0,0)这个点,然后遍历到下一条对角线上的点(0,1),(1,0)((0,1),(1,0)是(0,0)的下一点);

2、遍历到下一条对角线上的所有点后,删除其父节点(如(0,0)),并依次从点(0,1),(1,0)开始遍历到下一条对角线,遍历完成后,删除其父节点;

3、一条对角线上的点可能会重复被遍历,如:(1,1)会依次被(0,1),(1,0)遍历子节点时遍历到,终点也是会被重复遍历的……次数则等于所有父节点的次数之和,具体实现则是先保存一个父节点的次数,从另一个父节点又遍历到该子节点时,取之前的次数和另一个父节点的次数求和……


四、面向对象建模

1、类图如下:

2、类的说明:

1)Position:位置对象,拥有坐标属性;

2)Chessboard:棋盘对象,限定了所有棋子的活动范围;

3)Chessman:棋子类,卒的抽象类,抽象出所有棋子的特征:有个起始位置、有自己的步伐,并具有可移动的行为,每个棋子有个地图,对应棋盘对象;

4)Pawn:卒对象,实现了所有可达路径的统计;

5)ChineseChess:象棋游戏对象,里面包含了棋盘和棋子(如卒),并定义了游戏的统一入口;


3、具体实现:

Position.java

/** * 坐标位置对象 *  * @author dobuy * @time 2013-5-12 */public class Position{private int x;private int y;public Position(int x, int y){super();this.x = x;this.y = y;}/** * 获取偏移后的位置 *  * @param offset 偏移量 * @return */public Position offset(Position offset){return new Position(getX() + offset.getX(), getY() + offset.getY());}/** * 偏移量的X,Y坐标交换位置 *  * @return */public Position reversal(){return new Position(getY(), getX());}public int getX(){return x;}public void setX(int x){this.x = x;}public int getY(){return y;}public void setY(int y){this.y = y;}@Overridepublic int hashCode(){final int prime = 31;int result = 1;result = prime * result + x;result = prime * result + y;return result;}@Overridepublic boolean equals(Object obj){if (this == obj)return true;if (obj == null)return false;if (getClass() != obj.getClass())return false;Position other = (Position) obj;if (x != other.x)return false;if (y != other.y)return false;return true;}@Overridepublic String toString(){return "Position [x=" + x + ", y=" + y + "]";}}
Chessboard.java

/** * 棋盘(棋子的地图) *  * @author dobuy * @time 2013-5-12 */public class Chessboard{/** * 棋盘的最小边界点(原点) */private Position origonPosition;/** * 棋盘的最大边界点 */private Position edgePosition;public Chessboard(Position edgePosition){this.origonPosition = new Position(0, 0);this.edgePosition = edgePosition;}/** * 当前位置在棋盘中是否越界 *  * @return */public boolean isOverEdge(Position currentPosition){if (currentPosition.getX() < getOrigonPosition().getX()|| currentPosition.getY() < getOrigonPosition().getY()|| currentPosition.getX() > getEdgePosition().getX()|| currentPosition.getY() > getEdgePosition().getY()){return true;}return false;}public Position getEdgePosition(){return edgePosition;}private Position getOrigonPosition(){return origonPosition;}}
Chessman.java

import java.util.ArrayList;import java.util.List;/** * 棋子类,描述棋子的位置属性及移动功能 *  * @author dobuy * @time 2013-5-12 */public abstract class Chessman{/** * 棋子拥有一张棋盘地图 */private Chessboard chessMap;/** * 起始位置 */private Position origonPos;/** * 移动的步伐向量,如卒的当前位置为(x,y),移动向量为(0,1),移动一次时, * 既可以表示向右移动一格(x+1,y+0),也可以表示向下移动一格(x+0,y+1) 即:约定移动的步伐向量不分横纵坐标 */private Position step;public Chessman(Position origonPos, Position step){this.origonPos = origonPos;this.step = step;}public List<Position> moveNext(){return moveNext(getOrigonPos());}/** * 从当前位置移动一步后的所有可能位置 *  * @param currentPosition 当前位置 * @return */public List<Position> moveNext(Position currentPosition){return getNextPositionByStep(currentPosition);}public void setChessMap(Chessboard chessMap){this.chessMap = chessMap;}public Position getOrigonPos(){return origonPos;}public Position getStep(){return step;}public Chessboard getChessMap(){return chessMap;}/** * 棋子是否越界,子类可扩展 *  * @param currentPosition 棋子当前位置 * @return */protected boolean isOverEdge(Position currentPosition){return getChessMap().isOverEdge(currentPosition);}/** * 棋子从起点一直移动到终点,并返回所有可能的路径总数 *  */protected abstract long move();/** * 棋子根据规则获取下一步的所有位置(可扩展,目前只有向右和向下) *  * @param currentPosition 棋子的当前位置 * @return */protected List<Position> getNextPositionByStep(Position currentPosition){List<Position> nextPositions = new ArrayList<Position>();Position nextPosition = currentPosition.offset(getStep());addNextPosition(nextPositions, nextPosition);nextPosition = currentPosition.offset(getStep().reversal());addNextPosition(nextPositions, nextPosition);return nextPositions;}/** * 向下一步集合中添加一个位置,越界则不添加 *  * @param nextPositions * @param nextPosition */private void addNextPosition(List<Position> nextPositions,Position nextPosition){if (!isOverEdge(nextPosition)){nextPositions.add(nextPosition);}}}
Pawn.java

import java.math.BigDecimal;import java.util.HashMap;import java.util.Iterator;import java.util.List;import java.util.Map;/** * 卒 *  * @author dobuy * @time 2013-5-12 */public class Pawn extends Chessman{Map<Position, Long> positionMap;public Pawn(Position origonPos, Position step){super(origonPos, step);positionMap = new HashMap<Position, Long>();}/* * (non-Javadoc) */@Overrideprotected long move(){getPositionMap().put(getOrigonPos(), 1L);try{countWays();}catch (StackOverflowError e){return -1;}Position edgePosition = getChessMap().getEdgePosition();if (getPositionMap().containsKey(edgePosition)){return getPositionMap().get(edgePosition);}return 0;}@Overrideprotected boolean isOverEdge(Position currentPosition){return getChessMap().isOverEdge(currentPosition);}/** * <pre> * 递归计算卒的下一步位置的经过次数 * 1、第一次取(0,0)点的次数; * 2、经过(0,0)点的次数也等于经过(0,1)、(1,0)的次数之和; * 3、经过(0,1)点的次数等于经过(1,1)、(0,2)的次数之和,同理(1,0)=(1,1)+(2,0) * …… * 经过如上可以观察得出结论: * 经过当前点X的次数=X所有下一点的次数之和,且所有下一点的次数和X的次数相同 *  * 算法: * 1、保存经过(0,0)点的次数为1,并保存至集合M中; * 2、遍历集合M中的所有点,假设当前点为X,遍历X的所有合法的下一点(不合法直接丢弃), * 1)若下一点N不在M中,则保存N至M中,其次数为X的次数; * 2)若N已经在集合M中,则N的次数为N的次数与X的次数之和; * 3)遍历完X的所有子节点后,从M中删除X节点; * 3、重复2,直至结合中为空或者只有终点 * </pre> */private void countWays() throws StackOverflowError{if (getPositionMap().isEmpty()|| getPositionMap().containsKey(getChessMap().getEdgePosition())){return;}// 为了避免Map<Position, Long> currentPositionMaps = new HashMap<Position, Long>();currentPositionMaps.putAll(getPositionMap());Iterator<Position> nextPosIterator = currentPositionMaps.keySet().iterator();Position currentPosition = null;List<Position> nextPositions = null;while (nextPosIterator.hasNext()){currentPosition = nextPosIterator.next();nextPositions = moveNext(currentPosition);for (Position nextPosition : nextPositions){addNextPosition(currentPosition, nextPosition);}getPositionMap().remove(currentPosition);}countWays();}/** * 把当前位置(C点)的下一步位置(N点)加入集合中,N点的经过次数为C点与N点的次数之和(N点不在集合中时,次数为C点次数) *  */private void addNextPosition(Position currentPosition, Position nextPosition)throws StackOverflowError{// 不合法位置,丢弃if (isOverEdge(nextPosition)){return;}long count = getPositionMap().get(currentPosition);if (getPositionMap().containsKey(nextPosition)){long currentCount = getPositionMap().remove(nextPosition);if (isAddResultSizeOverflow(count, currentCount)){throw new StackOverflowError("It's too big number to count ways!");}count = count + currentCount;}getPositionMap().put(nextPosition, count);}/** * 判断2个long型变量之和是否超出Long的范围 *  * isAddResultSizeOverflow(这里用一句话描述这个方法的作用) */private boolean isAddResultSizeOverflow(long count1, long count2){BigDecimal countDecimal1 = BigDecimal.valueOf(count1);BigDecimal countDecimal2 = BigDecimal.valueOf(count2);BigDecimal sum = countDecimal1.add(countDecimal2);BigDecimal max = BigDecimal.valueOf(Long.MAX_VALUE);return sum.compareTo(max) >= 0;}private Map<Position, Long> getPositionMap(){return positionMap;}}
ChineseChess.java
/** * 中国象棋 *  * @author dobuy * @time 2013-5-12 */public class ChineseChess{/** * 卒 */private Pawn pawn;/** * 地图 */private Chessboard chessboard;/** * 启动入口 *  * @param edgePoint 最大边界的坐标数组 * @return */public long startGame(int[] edgePoint){if (edgePoint == null || edgePoint.length != 2){return -1;}Position edgePosition = new Position(edgePoint[0], edgePoint[1]);return startGame(edgePosition);}/** * 真正的入口 *  * @return */private long startGame(Position edgePosition){if (edgePosition.getX() <= 0 || edgePosition.getY() <= 0){return -1;}init(edgePosition);return getPawn().move();}private void init(Position edgePosition){this.chessboard = new Chessboard(edgePosition);this.pawn = new Pawn(new Position(0, 0), new Position(0, 1));getPawn().setChessMap(getChessboard());}private Pawn getPawn(){return pawn;}private Chessboard getChessboard(){return chessboard;}}
单元测试类:

import static org.junit.Assert.assertEquals;import org.junit.Before;import org.junit.Test;/** *  * 类名称:ChineseChessTest 类描述: 创建人:dobuy *  */public class ChineseChessTest{private ChineseChess chineseChess;private int[] edgePoint;@Beforepublic void before(){chineseChess = new ChineseChess();}/** * Testcase1:正常流程 *  */@Testpublic void testStartGame1(){edgePoint = new int[] { 2, 2 };assertEquals(chineseChess.startGame(edgePoint), 6);}/** * Testcase2:正常流程(大数据) *  */@Testpublic void testStartGame2(){edgePoint = new int[] { 30, 30 };assertEquals(chineseChess.startGame(edgePoint), 118264581564861424L);}/** * Testcase3:异常流程:参数越界非法 */@Testpublic void testStartGame3(){edgePoint = new int[] { -1, 2 };assertEquals(chineseChess.startGame(edgePoint), -1);}/** * Testcase4:异常流程:参数位数非法 */@Testpublic void testStartGame4(){edgePoint = new int[] { 2 };assertEquals(chineseChess.startGame(edgePoint), -1);}/** * Testcase5:异常流程:大数据越界 */@Testpublic void testStartGame5(){edgePoint = new int[] { 50, 50 };assertEquals(chineseChess.startGame(edgePoint), -1);}}
经过验证,单元测试全部运行通过;

下一篇:马拦过河卒(Java实现),完全可以重用现在的代码;事实是先实现了马拦过河卒再实现这个卒的移动问题的……

欢迎批评指正!




原创粉丝点击