八皇后問題,是一個古老而著名的問題,是回溯算法的典型案例。該問題是國際西洋棋棋手馬克斯·貝瑟爾於1848年提出:在8×8格的國際象棋上擺放八個皇后,使其不能互相攻擊,即任意兩個皇后都不能處於同一行、同一列或同一斜線上,問有多少種擺法。 高斯認爲有76種方案。1854年在柏林的象棋雜誌上不一樣的做者發表了40種不一樣的解,後來有人用圖論的方法解出92種結果。計算機發明後,有多種計算機語言能夠解決此問題。---------以上節選自百度百科。java
算法思考,初步思路:算法
構建二維int或者short型數組,內存中模擬棋盤數組
chess[r][c]=0表示:r行c列沒有皇后,chess[r][c]=1表示:r行c列位置有一個皇后安全
從第一行第一列開始逐行擺放皇后函數
依題意每行只能有一個皇后,遂逐行擺放,每行一個皇后便可性能
擺放後當即調用一個驗證函數(傳遞整個棋盤的數據),驗證合理性,安全則擺放下一個,不安全則嘗試擺放這一行的下一個位置,直至擺到棋盤邊界url
當這一行全部位置都沒法保證皇后安全時,須要回退到上一行,清除上一行的擺放記錄,而且在上一行嘗試擺放下一位置的皇后(回溯算法的核心)spa
當擺放到最後一行,而且調用驗證函數肯定安全後,累積數自增1,表示有一個解成功算出code
驗證函數中,須要掃描當前擺放皇后的左上,中上,右上方向是否有其餘皇后,有的話存在危險,沒有則表示安全,並不須要考慮當前位置棋盤下方的安全性,由於下面的皇后尚未擺放blog
回溯算法的自然實現是使用編譯器的遞歸函數,可是其性能使人遺憾
下面咱們使用上面的思路初步實現8皇后的問題解法,而且將全部解法打印出來,供咱們驗證正確性
import java.util.Date; /** * 在8×8格的國際象棋上擺放八個皇后,使其不能互相攻擊, * 即任意兩個皇后都不能處於同一行、同一列或同一斜線上,問有多少種擺法。 * 下面使用遞歸方法解決 * @author newflydd@189.cn * */ public class EightQueen { private static final short N=8; //使用常量來定義,方便以後解N皇后問題 private static int count=0; //結果計數器 public static void main(String[] args) { Date begin =new Date(); //初始化棋盤,所有置0 short chess[][]=new short[N][N]; for(int i=0;i<N;i++){ for(int j=0;j<N;j++){ chess[i][j]=0; } } putQueenAtRow(chess,0); Date end =new Date(); System.out.println("解決 " +N+ " 皇后問題,用時:" +String.valueOf(end.getTime()-begin.getTime())+ "毫秒,計算結果:"+count); } private static void putQueenAtRow(short[][] chess, int row) { /** * 遞歸終止判斷:若是row==N,則說明已經成功擺放了8個皇后 * 輸出結果,終止遞歸 */ if(row==N){ count++; System.out.println("第 "+ count +" 種解:"); for(int i=0;i<N;i++){ for(int j=0;j<N;j++){ System.out.print(chess[i][j]+" "); } System.out.println(); } return; } short[][] chessTemp=chess.clone(); /** * 向這一行的每個位置嘗試排放皇后 * 而後檢測狀態,若是安全則繼續執行遞歸函數擺放下一行皇后 */ for(int i=0;i<N;i++){ //擺放這一行的皇后,以前要清掉全部這一行擺放的記錄,防止污染棋盤 for(int j=0;j<N;j++) chessTemp[row][j]=0; chessTemp[row][i]=1; if( isSafety( chessTemp,row,i ) ){ putQueenAtRow(chessTemp,row+1); } } } private static boolean isSafety(short[][] chess,int row,int col) { //判斷中上、左上、右上是否安全 int step=1; while(row-step>=0){ if(chess[row-step][col]==1) //中上 return false; if(col-step>=0 && chess[row-step][col-step]==1) //左上 return false; if(col+step<N && chess[row-step][col+step]==1) //右上 return false; step++; } return true; } }
輸出結果:
須要打印棋盤時,耗時34毫秒,再看一看不須要打印棋盤時的性能:
耗時2毫秒,性能感受還能夠。
你覺得到這兒就結束了嗎?高潮還沒開始,下面咱們來看看這種算法解決九、十、11...15皇后問題的性能
稍微變更一下代碼,循環打印出各個解的結果,以下圖所示:
當我開始嘗試解決16皇后問題時,發現時間複雜度已經超出個人內心預期,最終沒讓它繼續執行下去。
上網一查N皇后的國際記錄,已經有科研單位給出了25皇后的計算結果,耗時暫未可知
咱們的程序跑16皇后已經無能爲力,跑15皇后已經捉襟見肘(87秒)
中國的一些算法高手能在100秒內跑16皇后,可見上面的算法效率只能說通常,辣麼,該如何改進呢?
咱們發現二維棋盤數據在內存中浪費嚴重,全是0和1的組成,同時每次遞歸時使用JAVA的clone函數克隆一個新的棋盤,消耗進一步擴大,這裏面必定有改進的方案。
咱們考慮將二維數組使用一維數組代替,將一維數組的下標數據利用起來,模仿棋盤結構,如chess[R]=C時,表示棋盤上R行C列有一個皇后
這樣程序的空間效率會獲得迅速提升,同時二維數據改變成一維數據後的遍歷過程也會大爲縮減,時間效率有所提升,下面貼出代碼:
import java.util.Date; public class EightQueen2 { private static final short K=15; //使用常量來定義,方便以後解N皇后問題 private static int count=0; //結果計數器 private static short N=0; public static void main(String[] args) { for(N=9;N<=K;N++){ Date begin =new Date(); /** * 初始化棋盤,使用一維數組存放棋盤信息 * chess[n]=X:表示第n行X列有一個皇后 */ short chess[]=new short[N]; for(int i=0;i<N;i++){ chess[i]=0; } putQueenAtRow(chess,(short)0); Date end =new Date(); System.out.println("解決 " +N+ "皇后問題,用時:" +String.valueOf(end.getTime()-begin.getTime())+ "毫秒,計算結果:"+count); } } private static void putQueenAtRow(short[] chess, short row) { /** * 遞歸終止判斷:若是row==N,則說明已經成功擺放了8個皇后 * 輸出結果,終止遞歸 */ if(row==N){ count++; // System.out.println("第 "+ count +" 種解:"); // for(int i=0;i<N;i++){ // for(int j=0;j<N;j++){ // System.out.print(chess[i][j]+" "); // } // System.out.println(); // } return; } short[] chessTemp=chess.clone(); /** * 向這一行的每個位置嘗試排放皇后 * 而後檢測狀態,若是安全則繼續執行遞歸函數擺放下一行皇后 */ for(short i=0;i<N;i++){ //擺放這一行的皇后 chessTemp[row]=i; if( isSafety( chessTemp,row,i ) ){ putQueenAtRow(chessTemp,(short) (row+1)); } } } private static boolean isSafety(short[] chess,short row,short col) { //判斷中上、左上、右上是否安全 short step=1; for(short i=(short) (row-1);i>=0;i--){ if(chess[i]==col) //中上 return false; if(chess[i]==col-step) //左上 return false; if(chess[i]==col+step) //右上 return false; step++; } return true; } }
運算結果:
能夠看到全部結果的耗時縮短了一倍有餘,這無疑是一次算法的進步
辣麼,還有改進空間嗎?
答案必然是確定的,對於算法,咱們越是精益求精,咱們的能力就越強大,咱們越是淺嘗輒止,咱們的進步就越慢。
下一篇博客咱們來繼續改進這個問題的算法,摒棄編譯器自帶的遞歸回溯,本身手寫回溯過程,相信效率會進一步提升,最終在可控範圍內將16皇后問題解出來。