java 马踏棋盘优化_我所知道的十大常用算法之马踏棋盘算法(深度搜索、贪心思想优化 )...
前言需求
今天我們學習的是馬踏棋盤算法,我們還是從一個場景里引入看看
馬踏棋盤算法也被稱為騎士周游問題
將馬隨機放在國際象棋的6×6棋盤Board0~5的某個方格中
提示:馬按走棋規則(馬走日字)進行移動
要求:每個方格只進入一次,走遍棋盤上全部64個方格
一、馬踏棋盤問題
馬踏棋盤問題(騎士周游問題)實際上是:圖的深度優先搜索(DFS)的應用
還記得圖的深度優先搜索(DFS)嗎?
有些模糊或者不記得小伙伴可以看往期文章:圖(廣度優先與深度優先)
那么按照我們的簡單思路,是不是要一個位置一個位置去踩坑看看?
那么按照我們的深度優先搜索,就要一步步走下去,直至達成任務
當我們的所選第三步的位置,無法達成完成任務
那么我們需要回溯,將原第三步更換到下一個位置里去
在以新第三步開始,進行搜索,也要一步步走下去,直至達成任務
二、通過示例來認識算法
根據我們之前簡單的思路,首先我們需要創建一個棋盤的數組
當我們做出選擇下一步的時候,我們需要將當前的位置標記為已訪問,并根據當前位置計算出馬兒能走那些位置,并放入到一個集合中里去
當然我們可以根據棋盤的情況來判斷是否可以進行計算
注意::馬兒不同的走法、會得到不同的結果,效率也會有影響(需優化)
規則判斷是否可走
那么我怎么知道這些位置是否可走呢?我是怎么計算出來的呢?
首先我們先分析當前位置的x、y坐標,按照規則進行計算:(馬走日字)
我們先分析一下象棋里的馬走日是怎么樣的吧
馬走日所說的是馬從提棋位置到落棋位置是一個“日”子的對角線,在沒有棋子踩住馬腳時,馬是可以隨意走哪個方向的日字都是可以的
在有其他棋子在馬的如圖相關位置時,馬就不能走該方向的日字了,我們也熟稱“踩馬腳了”。注意無論踩馬腳的棋子是己方的棋子還是敵方的棋子,被踩方向的日字都不能走了
如果四只馬腳都被踩了,那么這只馬哪里都走不了了(如圖)
在我們這個問題中,還請你看圖關聯看懂馬兒怎么走的,即稱馬走日
當我們知道規則怎么玩了,就可以從圖上看出來,每個點與當前點的關系
那么我們的馬兒剩下的點與當前是什么關系呢?怎么走呢?
騎士周游算法思路
我們創建一個類存放棋盤行、列,并記錄棋盤上的是否被訪問過public class HorseChessboard {
private static int x;//棋盤的列數
private static int y;//棋盤的行數
//創建一個數組,標記棋盤的各個位置是否被訪問過
private static boolean visited[];
//使用一個屬性,標記是否棋盤的所有位置都被訪問
private static boolean finished; // 如果為true,表示成功
}
我們使用Point 類來表示 (x, y) 坐標空間中的位置的點public class Point extends Point2D implements java.io.Serializable {
public int x;
public int y;
private static final long serialVersionUID = -5276940640259749850L;
public Point() {
this(0, 0);
}
public Point(Point p) {
this(p.x, p.y);
}
public Point(int x, int y) {
this.x = x;
this.y = y;
}
//以雙精度型返回點的 X 坐標。
public double getX() {
return x;
}
//以雙精度型返回點的 Y 坐標。
public double getY() {
return y;
}
//返回此點的位置。
@Transient
public Point getLocation() {
return new Point(x, y);
}
//將點的位置設為指定位置
public void setLocation(Point p) {
setLocation(p.x, p.y);
}
//將此點更改為具有指定位置
public void setLocation(int x, int y) {
move(x, y);
}
//將此點的位置設為指定的雙精度坐標
public void setLocation(double x, double y) {
this.x = (int) Math.floor(x+0.5);
this.y = (int) Math.floor(y+0.5);
}
//將此點移動到 (x,y) 坐標平面中的指定位置。
public void move(int x, int y) {
this.x = x;
this.y = y;
}
//平移 (x,y) 位置的點,沿 x 軸平移 dx,沿 y 軸平移 dy,移動后得到點 (x+dx, y+dy)
public void translate(int dx, int dy) {
this.x += dx;
this.y += dy;
}
//確定兩個點是否相等。
public boolean equals(Object obj) {
if (obj instanceof Point) {
Point pt = (Point)obj;
return (x == pt.x) && (y == pt.y);
}
return super.equals(obj);
}
// 返回此點的字符串表示形式及其在 (x,y) 坐標空間中的位置
public String toString() {
return getClass().getName() + "[x=" + x + ",y=" + y + "]";
}
}
根據思路,需要根據當前位置判斷馬兒能走那些位置,并將結果放入ArrayList集合中
public class HorseChessboard {
//省略其他關鍵性代碼....
/**
* 功能:根據當前位置(Point對象),計算馬兒還能走哪些位置(Point),并放入到一個集合中(ArrayList),最多有8個位置
* @param curPoint
* @return
*/
public static ArrayList next(Point curPoint){
ArrayList ps = new ArrayList<>();
//創建一個點
Point p1 = new Point();
//判斷馬兒是否能走5的位置
if((p1.x = curPoint.x - 2) >=0 && (p1.y = curPoint.y+1) >=0 ){
ps.add(new Point(p1));
}
return ps;
}
}
而其他點的位置與當前位置關系,我們之前也使用圖解的方式分析,現在代碼實現
public class HorseChessboard {
//省略其他關鍵性代碼....
/**
* 功能:根據當前位置(Point對象),計算馬兒還能走哪些位置(Point),并放入到一個集合中(ArrayList),最多有8個位置
* @param curPoint
* @return
*/
public static ArrayList next(Point curPoint){
ArrayList ps = new ArrayList<>();
//創建一個點
Point p1 = new Point();
//表示馬兒可以走5這個位置
if((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y -1) >= 0) {
ps.add(new Point(p1));
}
//判斷馬兒可以走6這個位置
if((p1.x = curPoint.x - 1) >=0 && (p1.y=curPoint.y-2)>=0) {
ps.add(new Point(p1));
}
//判斷馬兒可以走7這個位置
if ((p1.x = curPoint.x + 1) < x && (p1.y = curPoint.y - 2) >= 0) {
ps.add(new Point(p1));
}
//判斷馬兒可以走0這個位置
if ((p1.x = curPoint.x + 2) < x && (p1.y = curPoint.y - 1) >= 0) {
ps.add(new Point(p1));
}
//判斷馬兒可以走1這個位置
if ((p1.x = curPoint.x + 2) < x && (p1.y = curPoint.y + 1) < y) {
ps.add(new Point(p1));
}
//判斷馬兒可以走2這個位置
if ((p1.x = curPoint.x + 1) < x && (p1.y = curPoint.y + 2) < y) {
ps.add(new Point(p1));
}
//判斷馬兒可以走3這個位置
if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y + 2) < y) {
ps.add(new Point(p1));
}
//判斷馬兒可以走4這個位置
if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y + 1) < y) {
ps.add(new Point(p1));
}
return ps;
}
}
那么會不會有小伙伴有疑惑??
為什么走五那個位置就要>=0呢,走七的位置就要
我們先分析走五的位置的時候,為什么要>=0
同理,小于x,小于y代表我們只選擇在棋盤內的點,超出的則不能走
騎士周游算法實踐
往期我們使用的是二維數組代表這個點是否被訪問過
但這里是36步都需要走一遍,那么我們其實可以使用一維數組進行表示
這樣我們可以是用公式:馬兒所在行 * 棋盤行 +馬兒所在列 = 馬兒下標 + 1public class HorseChessboard {
//省略其他關鍵性代碼....
/**
* 完成騎士周游問題的算法
* @param chessboard 棋盤
* @param row 馬兒當前的位置的行 從0開始
* @param column 馬兒當前的位置的列 從0開始
* @param step 是第幾步 ,初始位置就是第1步
*/
public static void traversalChessboard(int[][] chessboard, int row, int column, int step) {
//標記當前棋盤執行的是第幾步
chessboard[row][column] = step;
//row = 3 X = 6 column = 3 = 3 * 6 + 3 = 21 -1 = 20
visited[row * x + column] = true; //標記該位置已經訪問
//獲取當前位置可以走的下一個位置的集合
ArrayList ps = next(new Point(column, row));
}
}
當我們獲取到當前位置可以走的下一個位置的集合,就進行遍歷遞歸public class HorseChessboard {
//省略其他關鍵性代碼....
/**
* 完成騎士周游問題的算法
* @param chessboard 棋盤
* @param row 馬兒當前的位置的行 從0開始
* @param column 馬兒當前的位置的列 從0開始
* @param step 是第幾步 ,初始位置就是第1步
*/
public static void traversalChessboard(int[][] chessboard, int row, int column, int step) {
//標記當前棋盤執行的是第幾步
chessboard[row][column] = step;
//row = 3 X = 6 column = 3 = 3 * 6 + 3 = 21 -1 = 20
visited[row * x + column] = true; //標記該位置已經訪問
//獲取當前位置可以走的下一個位置的集合
ArrayList ps = next(new Point(column, row));
//遍歷 ps
while(!ps.isEmpty()) {
Point p = ps.remove(0);//取出下一個可以走的位置
//判斷該點是否已經訪問過
if(!visited[p.y * X + p.x]) {//說明還沒有訪問過
traversalChessboard(chessboard, p.y, p.x, step + 1);
}
}
}
}
我們怎么區分當前節點的可以走的下一個位置的集合,是否一路就成功了呢?
使用step 和 應該走的步數比較:step = X * Y
假如當前節點的可以走的下一個位置的集合,沒有一路就成功,怎么辦?
取消該位置已訪問,并將棋盤置為0,說明該節點處于回溯狀態public class HorseChessboard {
//省略其他關鍵性代碼....
/**
* 完成騎士周游問題的算法
* @param chessboard 棋盤
* @param row 馬兒當前的位置的行 從0開始
* @param column 馬兒當前的位置的列 從0開始
* @param step 是第幾步 ,初始位置就是第1步
*/
public static void traversalChessboard(int[][] chessboard, int row, int column, int step) {
chessboard[row][column] = step;
//row = 4 X = 8 column = 4 = 4 * 8 + 4 = 36
visited[row * x + column] = true; //標記該位置已經訪問
//獲取當前位置可以走的下一個位置的集合
ArrayList ps = next(new Point(column, row));
//遍歷 ps
while(!ps.isEmpty()) {
Point p = ps.remove(0);//取出下一個可以走的位置
//判斷該點是否已經訪問過
if(!visited[p.y * x + p.x]) {//說明還沒有訪問過
traversalChessboard(chessboard, p.y, p.x, step + 1);
}
}
//判斷馬兒是否完成了任務,使用 step 和應該走的步數比較 ,
//如果沒有達到數量,則表示沒有完成任務,將整個棋盤置0
//說明: step < X * Y 成立的情況有兩種
//1. 棋盤到目前位置,仍然沒有走完
//2. 棋盤處于一個回溯過程
if(step < x * y && !finished ) {
chessboard[row][column] = 0;
visited[row * x + column] = false;
} else {
finished = true;
}
}
}
接下來,讓我們使用demo 測試一把這些思路與代碼
我們采用上圖的馬兒作為起始位置,來測試看看public class HorseChessboard {
//省略其他關鍵性代碼....
public static void main(String[] args) {
System.out.println("騎士周游算法,開始運行~~");
//測試騎士周游算法是否正確
x = 6;
y = 6;
int row = 4; //馬兒初始位置的行,從1開始編號
int column = 3; //馬兒初始位置的列,從1開始編號
//創建棋盤
int[][] chessboard = new int[x][y];
visited = new boolean[x * y];//初始值都是false
//測試一下耗時
long start = System.currentTimeMillis();
traversalChessboard(chessboard, row - 1, column - 1, 1);
long end = System.currentTimeMillis();
System.out.println("共耗時: " + (end - start) + " 毫秒");
//輸出棋盤的最后情況
for(int[] rows : chessboard) {
for(int step: rows) {
System.out.print(step + "\t");
}
System.out.println();
}
}
}
運行結果如下:
騎士周游算法,開始運行~~
共耗時: 40 毫秒
08 03 10 29 32 05
17 28 07 04 11 30
02 09 18 31 06 33
27 16 01 20 23 12
36 19 14 25 34 21
15 26 35 22 13 24
三、使用貪心思想進行優化
利用貪心算法的思想,對下一步的所有集合的數目, 進行非遞減排序
什么是非遞減?
遞增的情況是:1、2、3、4、5、6、7、8、9
遞減的情況是:9、8、7、6、5、4、3、2、1
非遞增的情況是:9、8、7、6、5、5、4、3、2、1
非遞減的情況是:1、2、2、3、3、4、4、5、6、7
目的:使馬兒走的下一步是下一步集合中可選性最少的,減少回溯可能性public class HorseChessboard {
//省略其他關鍵性代碼....
//根據當前這個一步的所有的下一步的選擇位置,進行非遞減排序, 減少回溯的次數
public static void sort(ArrayList ps) {
ps.sort(new Comparator() {
@Override
public int compare(Point o1, Point o2) {
// TODO Auto-generated method stub
//獲取到o1的下一步的所有位置個數
int count1 = next(o1).size();
//獲取到o2的下一步的所有位置個數
int count2 = next(o2).size();
if(count1 < count2) {
return -1;
} else if (count1 == count2) {
return 0;
} else {
return 1;
}
}
});
}
}
那么怎么使用呢,我們在算法里進行排序優化public class HorseChessboard {
//省略其他關鍵性代碼....
/**
* 完成騎士周游問題的算法
* @param chessboard 棋盤
* @param row 馬兒當前的位置的行 從0開始
* @param column 馬兒當前的位置的列 從0開始
* @param step 是第幾步 ,初始位置就是第1步
*/
public static void traversalChessboard(int[][] chessboard, int row, int column, int step) {
//標記當前棋盤執行的是第幾步
chessboard[row][column] = step;
//row = 3 X = 6 column = 3 = 3 * 6 + 3 = 21 -1 = 20
visited[row * x + column] = true; //標記該位置已經訪問
//獲取當前位置可以走的下一個位置的集合
ArrayList ps = next(new Point(column, row));
//對ps進行排序,排序的規則就是對ps的所有的Point對象的下一步的位置的數目,進行非遞減排序
sort(ps);
//遍歷 ps
while(!ps.isEmpty()) {
Point p = ps.remove(0);//取出下一個可以走的位置
//判斷該點是否已經訪問過
if(!visited[p.y * X + p.x]) {//說明還沒有訪問過
traversalChessboard(chessboard, p.y, p.x, step + 1);
}
}
}
}public class HorseChessboard {
//省略其他關鍵性代碼....
public static void main(String[] args) {
System.out.println("騎士周游算法,開始運行~~");
//測試騎士周游算法是否正確
x = 6;
y = 6;
int row = 4; //馬兒初始位置的行,從1開始編號
int column = 3; //馬兒初始位置的列,從1開始編號
//創建棋盤
int[][] chessboard = new int[x][y];
visited = new boolean[x * y];//初始值都是false
//測試一下耗時
long start = System.currentTimeMillis();
traversalChessboard(chessboard, row - 1, column - 1, 1);
long end = System.currentTimeMillis();
System.out.println("共耗時: " + (end - start) + " 毫秒");
//輸出棋盤的最后情況
for(int[] rows : chessboard) {
for(int step: rows) {
System.out.print(step + "t");
}
System.out.println();
}
}
}
運行結果如下:
騎士周游算法,開始運行~~
共耗時: 9 毫秒
08 03 10 29 32 05
17 28 07 04 11 30
02 09 18 31 06 33
27 16 01 20 23 12
36 19 14 25 34 21
15 26 35 22 13 24
從40毫秒 到9毫秒 這個速度還是很客觀的,相比之前的算法更優一些
總結
以上是生活随笔為你收集整理的java 马踏棋盘优化_我所知道的十大常用算法之马踏棋盘算法(深度搜索、贪心思想优化 )...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java记录登陆时间_Spring se
- 下一篇: java数组使用实验报告_JAVA数组与