馬攔過河卒(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
Chessboard.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 + "]"; } }
/**
* 棋盤(棋子的地圖)
*
* @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實現) ,完全可以重用現在的程式碼;事實是先實現了馬攔過河卒再實現這個卒的移動問題的……
歡迎批評指正!