一、问题描述:
1、在部分的象棋棋盘(都是方格,大小可从键盘输入)中,假设卒只能向下或者向右移动,且卒在原点A(0,0)位置
2、棋盘中有马(初始位置由键盘输入),马可向任意方向移动一步,包括其初始位置都是卒的禁区
3、约定B点不同于A点,且不同于马的初始位置;
4、求卒移动到棋盘最大的终点位置B(m,n)的所有路径数;
如图所示:假设马在其中标注的中心黑点处,则其所有标注的黑点都为卒的禁区(玩过象棋的朋友就会知道这是什么意思)
解题方法同 卒的移动问题(JAVA)
只不过在一步一步遍历时,多了一些限制条件(有些点无法经过)。
代码改动说明:
1、增加一个Horse类,它继承了Chessman这个棋子类,它可向8个方向移动一步,获取下一步位置的方式也有所改变;
2、
1)Pawn(卒)类里增加一个违禁位置集合,当在移动过程中遇到集合中的点时这条路径就结束了(其它方向上的路径继续);
2)Pawn复写了父类Chessman的判定越界方法,即增加了违禁位置的判断;
3)ChessGame中增加了Horse的实例,并在马移动一步后,将其起始位置即所有下一步的位置设置到卒的违禁位置集合中,其它代码基本上没有变化;
4、增加马的输入参数即校验;
代码如下:
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;
}
public boolean isOverEdge()
{
return isOverEdge(getOrigonPos());
}
/**
* 棋子是否越界,子类可扩展
*
* @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)
{
return getNextPositionByStep(currentPosition, getStep());
}
protected List<Position> getNextPositionByStep(Position currentPosition,
Position step)
{
List<Position> nextPositions = new ArrayList<Position>();
Position nextPosition = currentPosition.offset(step);
addNextPosition(nextPositions, nextPosition);
nextPosition = currentPosition.offset(step.reversal());
addNextPosition(nextPositions, nextPosition);
return nextPositions;
}
/**
* 向下一步集合中添加一个位置,越界则不添加
*
* @param nextPositions
* @param nextPosition
*/
protected void addNextPosition(List<Position> nextPositions,
Position nextPosition)
{
if (!isOverEdge(nextPosition))
{
nextPositions.add(nextPosition);
}
}
/**
* 获取棋盘的最大边界值
*
*/
protected Position getEdgePosition()
{
return getChessMap().getEdgePosition();
}
}
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;
private List<Position> illegalPositions;
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;
}
public void setIllegalPositions(List<Position> illegalPositions)
{
this.illegalPositions = illegalPositions;
}
@Override
protected boolean isOverEdge(Position currentPosition)
{
boolean isOverEdge = super.isOverEdge(currentPosition);
// 本次实现的原则是先移动,再判断马的范围:
// 1、先判定是否到达终点,再判定是否在马的范围;
// 2、即使起点在马的覆盖范围,卒先移动一步就超出这个范围了
if (currentPosition.equals(getOrigonPos())
|| currentPosition.equals(getEdgePosition()))
{
return isOverEdge;
}
return getIllegalPositions().contains(currentPosition) || isOverEdge;
}
/**
* <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;
}
private List<Position> getIllegalPositions()
{
return illegalPositions;
}
}
Horse.java
import java.util.List;
/**
*
* 类名称:Horse 类描述: 创建人:dobuy
*
*/
public class Horse extends Chessman
{
/**
* 创建一个新的实例 Horse.
*
* @param origonPos
* @param step
*/
public Horse(Position origonPos, Position step)
{
super(origonPos, step);
}
/**
* 获取下一步的8个方向上的位置
*/
@Override
protected List<Position> getNextPositionByStep(Position currentPosition)
{
// 只有向右和向下2个方向(偏移量分别为(x,y)、(y,x),x,y互换的偏移量用一个点表示),扩展其它6个方向
List<Position> nextPositions = super
.getNextPositionByStep(currentPosition);
// 另6个方向可用如下三个偏移量表示
Position[] steps = new Position[] {
new Position(-getStep().getX(), getStep().getY()),
new Position(-getStep().getX(), -getStep().getY()),
new Position(getStep().getX(), -getStep().getY()) };
List<Position> nextPossiblePositions = null;
for (Position step : steps)
{
nextPossiblePositions = getNextPositionByStep(currentPosition, step);
addPossiblePositions(nextPositions, nextPossiblePositions);
}
return nextPositions;
}
@Override
protected long move()
{
return 0;
}
/**
* 添加下一步可能的几个点
*
*/
private void addPossiblePositions(List<Position> nextPositions,
List<Position> nextPossiblePositions)
{
for (Position nextPossiblePosition : nextPossiblePositions)
{
addNextPosition(nextPositions, nextPossiblePosition);
}
}
}
ChessGame.java
import java.util.List;
/**
* 中国象棋
*
* @author dobuy
* @time 2013-5-12
*/
public class ChineseChess
{
/**
* 卒
*/
private Pawn pawn;
/**
* 马
*/
private Horse horse;
/**
* 地图
*/
private Chessboard chessboard;
private static final int ERROR = -1;
/**
* 启动入口
*
* @param edgePoint 最大边界的坐标数组
* @return
*/
public long startGame(int[] edgePoint, int[] horsePosition)
{
if (isIllegalParams(edgePoint) || isIllegalParams(horsePosition))
{
return ERROR;
}
Position edgePosition = new Position(edgePoint[0], edgePoint[1]);
Position horseOriginPos = new Position(horsePosition[0],
horsePosition[1]);
return startGame(edgePosition, horseOriginPos);
}
/**
* 真正的入口
*
* @return
*/
private long startGame(Position edgePosition, Position horseOriginPos)
{
Position originPos = new Position(0, 0);
// 约定起点、终点、马的初始点,3者不能相等
if (edgePosition.equals(horseOriginPos)
|| edgePosition.equals(originPos)
|| horseOriginPos.equals(originPos))
{
return ERROR;
}
init(edgePosition, horseOriginPos);
if (horse.isOverEdge())
{
return ERROR;
}
return getPawn().move();
}
private void init(Position edgePosition, Position horseOriginPos)
{
this.chessboard = new Chessboard(edgePosition);
this.pawn = new Pawn(new Position(0, 0), new Position(0, 1));
horse = new Horse(horseOriginPos, new Position(1, 2));
getPawn().setChessMap(getChessboard());
horse.setChessMap(getChessboard());
List<Position> horsePositions = horse.moveNext();
horsePositions.add(horseOriginPos);
getPawn().setIllegalPositions(horsePositions);
}
private Pawn getPawn()
{
return pawn;
}
private Chessboard getChessboard()
{
return chessboard;
}
private boolean isIllegalParams(int[] position)
{
if (position == null || position.length != 2 || position[0] < 0
|| position[1] < 0)
{
return true;
}
return false;
}
}
单元测试类:
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;
private int[] horsePoint;
@Before
public void before()
{
chineseChess = new ChineseChess();
}
/**
* Testcase1:正常流程
*
*/
@Test
public void testStartGame1()
{
edgePoint = new int[] { 6, 6 };
horsePoint = new int[] { 2, 1 };
assertEquals(chineseChess.startGame(edgePoint, horsePoint), 36);
}
/**
* Testcase2:正常流程(大数据)
*
*/
@Test
public void testStartGame2()
{
edgePoint = new int[] { 30, 30 };
horsePoint = new int[] { 30, 0 };
assertEquals(chineseChess.startGame(edgePoint, horsePoint),
118264581564836135L);
}
/**
* Testcase2:正常流程(临界值:无法到达终点)
*
*/
@Test
public void testStartGame3()
{
edgePoint = new int[] { 30, 30 };
horsePoint = new int[] { 28, 28 };
assertEquals(chineseChess.startGame(edgePoint, horsePoint), 0);
}
/**
* Testcase4:异常流程:参数越界非法
*/
@Test
public void testStartGame4()
{
edgePoint = new int[] { -1, 2 };
horsePoint = new int[] { 2, 2 };
assertEquals(chineseChess.startGame(edgePoint, horsePoint), -1);
}
/**
* Testcase5:异常流程:参数位数非法
*/
@Test
public void testStartGame5()
{
edgePoint = new int[] { 2 };
horsePoint = new int[] { 2, 2 };
assertEquals(chineseChess.startGame(edgePoint, horsePoint), -1);
}
/**
* Testcase6:异常流程:大数据越界
*/
@Test
public void testStartGame6()
{
edgePoint = new int[] { 50, 50 };
horsePoint = new int[] { 2, 20 };
assertEquals(chineseChess.startGame(edgePoint, horsePoint), -1);
}
/**
* Testcase7:异常流程:马越界
*/
@Test
public void testStartGame7()
{
edgePoint = new int[] { 50, 50 };
horsePoint = new int[] { 2, 60 };
assertEquals(chineseChess.startGame(edgePoint, horsePoint), -1);
}
}