卒的移动问题(JAVA)

一、问题描述:

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实现) ,完全可以重用现在的代码;事实是先实现了马拦过河卒再实现这个卒的移动问题的……

欢迎批评指正!




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值