一、问题描述:
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;
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
@Override
public 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;
}
@Override
public 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)
*/
@Override
protected 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;
}
@Override
protected 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;
@Before
public void before()
{
chineseChess = new ChineseChess();
}
/**
* Testcase1:正常流程
*
*/
@Test
public void testStartGame1()
{
edgePoint = new int[] { 2, 2 };
assertEquals(chineseChess.startGame(edgePoint), 6);
}
/**
* Testcase2:正常流程(大数据)
*
*/
@Test
public void testStartGame2()
{
edgePoint = new int[] { 30, 30 };
assertEquals(chineseChess.startGame(edgePoint), 118264581564861424L);
}
/**
* Testcase3:异常流程:参数越界非法
*/
@Test
public void testStartGame3()
{
edgePoint = new int[] { -1, 2 };
assertEquals(chineseChess.startGame(edgePoint), -1);
}
/**
* Testcase4:异常流程:参数位数非法
*/
@Test
public void testStartGame4()
{
edgePoint = new int[] { 2 };
assertEquals(chineseChess.startGame(edgePoint), -1);
}
/**
* Testcase5:异常流程:大数据越界
*/
@Test
public void testStartGame5()
{
edgePoint = new int[] { 50, 50 };
assertEquals(chineseChess.startGame(edgePoint), -1);
}
}
经过验证,单元测试全部运行通过;
下一篇:马拦过河卒(Java实现) ,完全可以重用现在的代码;事实是先实现了马拦过河卒再实现这个卒的移动问题的……
欢迎批评指正!