在8*8的國際象棋棋盤上,皇后是威力較大的棋子,它能夠攻擊到與本身同行、同列以及同一斜線上的棋子,以下圖,全部橙色格子上的棋子,均可能會被皇后攻擊:java
而八皇后問題就是在8*8的棋盤上,找到合適的位置放置8個皇后,讓它們不會相互攻擊,並且須要找出這樣的放法共有多少種。算法
回溯法就是當咱們肯定了一個問題的解空間的結構後,從根節點出發,以深度優先的方式去遍歷解空間,找到合適的解。因此用此方法分析八皇后問題以下:數組
將棋盤看做0-7的平面直角座標系,八皇后問題的解就是尋找八個點的座標(i,j)。先拋開具體問題的約束來看,咱們要尋找的第一個座標有64中可能,當第一個座標肯定後,第二個座標也有64中可能,第三個一樣如此......所以,全部可能的解造成了一個64叉樹(類比二叉樹的說法),這就是有可能的解(64^8種可能),解空間結構是64叉樹形狀的。jvm
基於此解空間的結構,才能以深度優先的方式去遍歷解空間,並尋找合適的解。工具
當咱們結合問題對解的約束來看,八皇后問題的解就是這個64叉樹上某些從根節點到葉子節點的路徑上的座標。具體約束就是皇后的攻擊規則(任意兩點不能在同一直線或斜線上)。ui
也就是將皇后一個個擺上去,當咱們擺到第n個皇后時,若發現按照約束條件,任何位置都不能擺放,那就說明第n-1個皇后在當前位置不能獲得正確的解,因此須要從新肯定第n-1個皇后的位置。若第n-1個皇后再無其餘位置可擺放,則須要從新肯定第n-2個皇后的位置......spa
這就是回溯遍歷解空間,在算法實現時,可使用遞歸或迭代進行回溯遍歷,分別被稱爲遞歸回溯和迭代回溯。code
算法使用名爲queen的二維int數組表示棋盤,數組的索引表示0-7的座標,值爲0表示空白,值爲1表示皇后的擺放位置。遞歸
因爲任意一行不能有兩個皇后,因此每一行必須擺放一個,所以程序在每一行依次嘗試:索引
package com.yawn.queen; /** * @author yawn */ public class QueenTest { private static final int SIZE = 8; private static int[][] queen = new int[SIZE][SIZE]; public static void main(String[] args) { for (int i = 0; i < SIZE; i++) { // 每一行都從0位置開始嘗試放置 place(i, 0); } printQueen("結果爲"); } /** * 第i行的放置 */ private static void place(int i, int start) { // 是否放置成功 boolean placed = false; for(int j = start; j < SIZE; j++) { if (check(i, j)) { queen[i][j] = 1; placed = true; printQueen("第 " + i + " 行放置"); break; } } if (!placed) { int index = seek(i - 1); // 去掉已經放置的皇后 queen[i-1][index] = 0; printQueen("回溯到第 " + (i-1) + " 行,從 " + (index+1) + " 開始"); place(i-1, index+1); place(i, 0); } } /// 如下爲工具方法,內容單一,淺顯易懂 /** * 輸出棋盤 */ private static void printQueen(String action) { System.out.println("---> " + action); for (int[] cells : queen) { for (int cell : cells) { System.out.print(cell + "\t"); } System.out.println(); } } /** * 尋找第i行放置皇后的位置 */ private static int seek(int i) { for (int j = 0; j < SIZE; j++) { if (queen[i][j] == 1) { return j; } } return -1; } /** * 檢查queen[i][j] 是否能夠放置皇后 */ private static boolean check(int i, int j) { for(int m = 0; m < SIZE; m++) { for(int n = 0; n < SIZE; n++) { if (queen[m][n]==1 && (i == m || j == n || Math.abs(i - m) == Math.abs(j - n))) { // queen[m][n]==1 :(m,n)位置有皇后 // i == m :同行 // j == n :同列 // Math.abs(i - m) == Math.abs(j - n) :同一斜線(兩個位置連線的斜率絕對值爲1) // 若是知足if條件,則(i,j)不可放置皇后 return false; } } } return true; } }
求解過程當中的輸出片斷以下:
---> 第 5 行放置 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ---> 回溯到第 5 行,從 3 開始 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ---> 第 5 行放置 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
咱們能夠看出:第5行放置在了(5,2)位置,而後嘗試放置第六行,結果第6行沒法放置,因此回溯到第5行,從(5,3)位置繼續嘗試放置,並在(5,3)放置成功。
以上代碼爲咱們找到了問題的一個解,但咱們想知道一共有多少解存在,這就須要咱們稍微修改代碼,具體以下:
在以上代碼中,若找到一個可行的解以後,程序就會執行結束。但咱們若要找到全部的解,就須要在找到可行解後,繼續讓程序嘗試下一個解便可。具體作法就是當最後一行均可以放置好皇后時,咱們只記錄這個解,而後再讓程序嘗試當前位置的下一個位置,而不退出程序。
當遍歷完全部可能的解以後,就能夠中止程序。因爲在遞歸調用過程當中,會產生很深的方法棧,致使棧滿報錯,因此棋盤不宜設置太大:
八皇后問題解的個數具體實現以下:
package com.yawn.queen; /** * @author yawn */ public class QueenTest2 { private static final int SIZE = 12; private static int count = 0; private static int[][] queen = new int[SIZE][SIZE]; public static void main(String[] args) { for (int i = 0; i < SIZE; i++) { place(i, 0); } } /** * 第i行的放置 */ private static void place(int i, int start) { if (i==0 && start== SIZE) { System.out.println("結果集已經遍歷完畢,共找到結果數爲:" + count); System.exit(0); } // 第i行是否放置成功 boolean rowPlaced = false; // 遍歷第i行每一個位置是否能夠放置皇后 for(int j = start; j < SIZE; j++) { if (check(i, j)) { queen[i][j] = 1; rowPlaced = true; // 最後一行放置成功,則獲得一個結果,打印結果後從下一位置繼續回溯遍歷結果集 if (i== SIZE -1) { count++; printQueen("第" + count + "個結果,從(" + (SIZE-1) + "," + (j+1) + ")繼續回溯尋找..."); // 去掉已經放置的皇后 queen[i][j] = 0; place(i, j+1); } break; } } // 若是第i行沒有放置成功,則回溯到第i-1行(從新放置第i-1行) if (!rowPlaced) { int index = seek(i - 1); reset(i-1, index); // 回溯放置第i-1行 place(i-1, index+1); // 直到第i-1行放置成功,再開始放置第i行(最終將全部結果集遍歷完畢後,只能使用exit(0)退出程序) place(i, 0); } } /// 如下爲工具方法,內容單一,淺顯易懂 /** * 輸出棋盤 */ private static void printQueen(String string) { System.out.println("---> " + string); for (int[] cells : queen) { for (int cell : cells) { System.out.print(cell + "\t"); } System.out.println(); } } /** * 去掉(i,j)位置已經放置的皇后 */ private static void reset(int i, int j) { queen[i][j] = 0; } /** * 尋找第i行放置皇后的位置 */ private static int seek(int i) { for (int j = 0; j < SIZE; j++) { if (queen[i][j] == 1) { return j; } } return -1; } /** * 檢查queen[i][j] 是否能夠放置皇后 */ private static boolean check(int i, int j) { for(int m = 0; m < SIZE; m++) { for(int n = 0; n < SIZE; n++) { if (queen[m][n]==1) { if (i == m || j == n || Math.abs(i - m) == Math.abs(j - n)) { return false; } } } } return true; } }
---> 第91個結果,從(7,4)繼續回溯尋找... 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 ---> 第92個結果,從(7,5)繼續回溯尋找... 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 結果集已經遍歷完畢,共找到結果數爲:92
最終棋盤規格爲8*8時,共有92個解。
原文參考做者的博客: