關於八皇后問題的解法

八皇后問題是十九世紀著名的數學家高斯1850年提出:在8X8格的國際象棋上擺放八個皇后,使其不能互相攻擊,即任意兩個皇后都不能處於同一行、同一列或同一斜線上,問有多少種擺法。

 

八皇后問題不是隻用於這個有點無聊的數學問題上,不然就沒什麼研究意義了。在景觀設計,道路佈局,城市規劃等方面有應用價值。比如佈置景觀時,要讓N個景觀互相不遮擋視線。

 

方案一  暴力法

最容易實現的,8層循環嵌套

當然OK,但它的複雜度驚人,O(nn)n爲行列數

爲了減少循環次數,針對該問題,最外層循環從第一列開始,向內層依次加1,最內層循環就從第八列開始了。

 

 

方案二  回溯法

八皇后問題是回溯法經典的例子

回溯法基本的思想是採用遞歸,並且分步解決問題

 

首先擺第一行的皇后(都是從第一列開始擺)

然後擺第二行的皇后,擺在第一列,發現不行;擺第二列,也不行;擺第三列,行了。

接下來再擺第三行,都是從第一列開始擺,如果不行就依次往後挪。

以此類推,對於某一行,如果這一行每列都不行,那麼就得退回到上一行,(說明上一行的位置就已經不對了),上一行的皇后得往後再挪一格,如果已經是最後一格了,那就再退到上上一行……

如果擺到了最後一行,並且出現了一個解,那麼將這個解輸出後,同樣再退回到上一行,將上一行的皇后往後挪一格……看看還有沒有其他解,直到不可能再出現其他解爲止。

 

棋盤信息,爲了減少每次棋子調整帶來的cost,我們可以用四個一維數組來表示。(傳統上用一個二維數組表示的話,每次棋子調整要改變數組中很多個元素的值,而且相當混亂)

 

因爲每個皇后的控制範圍爲一行、一列再加對角線,所以與之對應的,我們把棋盤分成8行、8列、15個左下斜對角線和15個右下斜對角線,每一項都用一個一維數組表示。每次擺一個皇后的時候,只需改變將與之對應的行、列、左下斜對角線和右下斜對角線數組中元素(說明此行、列、對角線上不能再放其他皇后了),也就是隻需改變4個元素,而且這4個元素的在它們的數組中的下標與該皇后的橫縱座標有關(具體可以自己推算一下)。

 

類聲明如下(cpp

class ChessBoard

{

public:

         ChessBoard();

         ChessBoard(int);

         void findSolutions();

private:

         const bool available;  //表示行、列或對角線是否可用

         const int squares, norm;  //squares爲行數,normsquares-1(用於推算對角線的下標)

         bool *column, *leftDiagonal, *rightDiagonal;

         int *positionInRow;  //記錄每一行皇后的下標(列號)

int howMany;  //已放好的皇后的個數

         void putQueen(int);

         void printBoard(ostream&);

         void initializeBoard();

};

 

關鍵方法的實現

void ChessBoard::putQueen(int row)

{

         int col;

         //對於每一行,都從第一格開始嘗試擺

         for(col=0; col<squares; col++)

         {

if(column[col]==available && leftDiagonal[row+col]==available && rightDiagonal[row-col+norm]==available)  //該格子可用

                   {

                            positionInRow[row]=col;

                            column[col]=!available;

                            leftDiagonal[row+col]=!available;

                            rightDiagonal[row-col+norm]=!available;

                  

                            if(row<squares-1)

                                     putQueen(row+1);  //進入下一行

                            else

                                     printBoard(cout);  //已找到一種解

                           

                            //由於下一行無法滿足條件或者已找到一種解而退回來時

//恢復原來的狀態,continue

column[col]=available;

                            leftDiagonal[row+col]=available;

                            rightDiagonal[row-col+norm]=available;

                   }

         }

}

 

其實用回溯法的話,也窮盡了每一種情況,也是exhaustive search,而且它的效率還不如暴力法,因爲用了遞歸。

 

 

方案三 Best-First Search

由於我們最初的問題是求所有的擺法,所以不管怎樣它一定是一個exhaustive search問題了。如果我們把問題改一下,對任意一種棋盤狀態(八行裏每行都只有一個皇后),怎樣才能最快的調整爲一個滿足條件的狀態。那這樣的話,就不能再像之前那樣盲目的去搜索了,得采用一定的策略了,就是Informed SearchBest-First SearchA*Hill ClimbingTabu Search等都能很好的解決該問題,我們以最基礎的Best-First Search來講述。

 

爲了簡化問題,我們先來看一個4X4的棋盤。

 

 

 

給定一個初始狀態,我們每次都對它挪動一個棋子(也就是擴展後繼節點)。第一張圖中,第一行的棋子能朝左或右挪一格,第二行只能朝右挪一格,第三行第四行都能朝左或右挪一格。圖中h表示深度,也就是調整的次數;g表示當前的Conflicts數,就是不符合的行、列和對角線總數。(圖中可能hg畫反了,真正的h應該代表當前節點離目標節點的cost,也就是圖中的g;真正的g應該代表初始節點到當前節點的cost,也就是圖中的h

根據Best-First Search算法,每次都將當前最好節點的所有後繼節點都插入OPEN List中,每次都選擇OPEN list中最好的節點進行擴展,直到選出的節點即爲目標節點(圖中的g=0)。每次都沿着當前最好的節點延伸,很快就能找到目標節點。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章