1. 程式人生 > >馬攔過河卒(Java實現)

馬攔過河卒(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實現) ,完全可以重用現在的程式碼;事實是先實現了馬攔過河卒再實現這個卒的移動問題的……

歡迎批評指正!