對八皇后解法分析

八皇后問題一向用來作爲回溯的樣例,這裏就稍微拋下自己當初的解法

問題:在一個8*8的棋盤上擺放八個皇后,要求皇后不能相互攻擊。

解法:這裏以C語言爲求解語言,先講解非遞歸方法,之後講解遞歸方法

 

由於皇后攻擊方向爲橫向,豎向,和斜線攻擊,問題也就簡化爲每排擺放一個皇后,

也即找到每排擺放皇后的位置。

 

一個簡單的思路就是可以利用11維數組來求解,定義爲全局變量,方便調用。

int arr[8];    // 用於每排皇后的位置,0-7表示第1列到第8列,初始化爲-1,表示無皇后

所以一個遍歷即可完成。

for( int row = 0; row < 8; ++row )

{

    // 這裏就是定位每排皇后的位置

}

 

那麼問題就直接集中在如何確定每排皇后的位置

由於每排皇后有8個擺放的位置,所以只能一個一個試,即

for( int col = 0; col < 8; ++col )

{

    if( 判斷該排,該列是否可以擺放皇后 )

    {

        可以擺放,就直接確定皇后的位置,然後跳出循環

        arr[row] = col;

        break;

    }

}

 

這裏的if判斷其實只用確定,第row排的皇后和之前的皇后是否衝突即可,

判斷也很簡單,只要不在同一列,而且斜線對角不成45度或135度即可,

所以可以抽離成一個函數單獨實現

// 用於判斷第row排第col列的皇后和前row排的皇后是否衝突

// 衝突返回true

bool judge(int row, int col) 

{

    for( int i = 0; i < row; ++i )

    {

        if( col == arr[i] ||  // 判斷是否位於同一列

            fabs( i - row ) == fabs( arr[i] - col ) ) // 判斷是否位於斜線上

            return true;

    }

    return false;

}

 

那麼上面的循環就很好寫了

for( int row = 0; row < 8; ++row )

{

    for( int col = 0; col < 8; ++col )

    {

        if( !judge( row, col ) )

        {

            arr[row] = col;

            break;

        }

    }

}

 

當然,做到這一步很簡單,但卻得不到正確的解,若是第row排每一列都不能擺放皇后,

則第row排產生不了結果

這裏就需要回溯判斷,

    1、因爲第row排都不能擺放,那麼就必須修改row-1排的皇后位置,

    2、然後再來擺放第row排的皇后,確定好位置,則繼續row+1排皇后的位置

    3、如果繼續不能擺放,則重複1,若第row-1排的皇后所有的位置換過之後,

       還是確定不了row排皇后的位置,那麼就繼續修改row-2排,直到可以,或者

       已達到第0排,還是不行,則結束查找。

    

那麼做出以下修改,加粗爲增加部分

for( int row = 0; row < 8; ++row )

{

    int col = 0; // col需要在for之外用到,因爲受作用域的限制,這裏需要提出來

    // 這裏需要有些處理,稍下分析

    

    for( ; col < 8; ++col )

    {

        if( !judge( row, col ) )

        {

            arr[row] = col;

            break;

        }

    }

    

    // 若是col == 8表示第row排皇后,已經嘗試過每一列,

    // 但是均不能擺放,需要重新擺放第row-1排的皇后

    if( col == 8 )

    {

        row -= 2; // 這裏需要-2,因爲for循環中有一個++row

    }

}

 

這裏只是單純回到了row-1排,卻沒有任何處理,因爲第row-1排之前已經放過皇后了(假設爲第y列),

所以現在row-1排的皇后只需要從第y+1列開始擺放即可,所以可以添加的處理是

if( arr[row] != -1 )     // 如果這是回溯的一排,那麼必然已經擺放過皇后了,那就單獨處理

{

    col = arr[row] + 1;    // 既然是回溯的,就令該排皇后從擺放在y+1列處

    arr[row] = -1;      // 既然是回溯的,就表示這排重新擺放,所以就直接令該排爲-1,表示沒擺皇后

}

 

好了,處理到了,這一步,如果不考慮邊界問題,基本上已經可以實現輸出一種情況

(1,1) (2,5) (3,8) (4,6) (5,3) (6,7) (7,2) (8,4) #1

這種結果恰巧不涉及到回溯2排的情況,所以,正好可以輸出,現在添加邊界處理

if( arr[row] != -1 )     // 如果這是回溯的一排,那麼必然已經擺放過皇后了,那就單獨處理

{

    col = arr[row] + 1;    // 既然是回溯的,就令該排皇后從擺放在y+1列處

    arr[row] = -1;      // 既然是回溯的,就表示這排重新擺放,所以就直接令該排爲-1,表示沒擺皇后

    

    // 添加邊界處理

    if( arr[row] == 7 ) // 都到了最後一列也不行,需要繼續回溯

    { 

        row -= 2;

        continue;

    }

}

到目前爲止,所以的處理都已經結束,處理了邊界,現在看下全貌

這裏去掉註釋和judge函數

void slove()
{
    for( int row = 0; row < 8; ++row )
    {
        int col = 0;

        if( arr[row] != -1 )
        {
            col = arr[row] + 1;
            arr[row] = -1;     

            if( arr[row] == 8 ) 
            { 
                row -= 2;
                continue;
            }
        }        

        for( ; col < 8; ++col )
        {
            if( !judge( row, col ) )
            {
                arr[row] = col;
                break;
            }
        }
     
        if( col == 8 )
        {
            row -= 2;
        }
    }
}


好了,再接再厲,實現全部結果輸出

要實現全部輸出,則需要在完成一次結果輸出之後,繼續回溯上一排,以祈求一次新的輸出

這樣重複直到回溯到第1排第8列這最後一個結束爲止,即可退出查找新的擺放方法

那麼修改如下

void slove()

{

    // 如果回溯到了第1排第8列,就退出

    if( arr[row] == 7 && row == 0 ) 

    {

        return ;

    }

    

    // 和原先內容一樣

    

    // 若是最後一排也擺放好皇后,則輸出該種擺法結果,然後繼續循環

    if( arr[row] != -1 && row = 7 )

    {

        disp();     // 一個輸出函數

        // 需要回溯到上一排,以祈求新的擺法

    }

}

這裏由於使用非遞歸,要實現這種跳轉,就只能採用goto,儘管我們學習中,都被告誡限制使用goto,但是

在不破壞程序結構的同時,採用一下也是可以的

最終結果如下

void slove()
{
    for( int row = 0; row < 8; ++row )
    {
        if( arr[row] == 7 && row == 0 ) 
        {
            return ;
        }
       

        label: // 跳到這裏,處理回溯的那一行
         int col = 0;
        if( arr[row] != -1 )
        {
            col = arr[row] + 1;
            arr[row] = -1;               

            if( arr[row] == 8 ) 
            { 
                row -= 2;
                continue;
            }
        }       

        for( ; col < 8; ++col )
        {
            if( !judge( row, col ) )
            {
                arr[row] = col;
                break;
            }
        }
        
        if( col == 8 )
        {
            row -= 2;
        }
        
        if( arr[row] != -1 && row == 7 )
        {
            disp();     
            goto label;
        }
    }
}


至此,關於非遞歸的解法就全部結束了

這裏將此封裝下,給下可以運行的全套代碼

/*
 * eighetPuzzle.cpp
 *
 * Created on : 2012-11-26
 *     Author : zwsatan
 */
#include <iostream>
#include <cmath>
using namespace std;

class PuzzleQueen
{
    int arr[8];
    bool judge(int row, int col) 
    {
        for( int i = 0; i < row; ++i )
        {
            if( col == arr[i] || fabs( i - row ) == fabs( arr[i] - col ) )
                return true;
        }
        return false;
    }
    
public:
    PuzzleQueen() 
    {
        for( int i = 0; i < 8; ++i )
            arr[i] = -1;
    }
    
    void slove()
    {
        for( int row = 0; row < 8; ++row )
        {
            if( arr[row] == 7 && row == 0 ) 
            {
                return ;
            }
            
            label:
            int col = 0;
            if( arr[row] != -1 )
            {
                col = arr[row] + 1;
                arr[row] = -1;     
                
                if( arr[row] == 8 ) 
                { 
                    row -= 2;
                    continue;
                }
            }
            
            for( ; col < 8; ++col )
            {
                if( !judge( row, col ) )
                {
                    arr[row] = col;
                    break;
                }
            }
            
            if( col == 8 )
            {
                row -= 2;
            }
            
            if( arr[row] != -1 && row == 7 )
            {
                disp();     
                goto label;
            }
        }
    }
    
    void disp()
    {
        static int count = 0;
        for( int i = 0; i < 8; ++i )
        {
            cout << "(" << i+1 << "," << arr[i] + 1 << ") ";
        }
        cout << "#" << ++count;
        cout << endl;
    }
};

int main(int argc, char *argv[])
{
    PuzzleQueen pq;
    pq.slove();
    
    return 0;
}


 

 

疲乏了嗎?

還是繼續接下來看遞歸的解法,`(*_*)

當然,我們絕大多數時間,對待回溯採用的都是遞歸方法,因此,我們在上述的方法上,稍作修改,

以使它成爲遞歸形式

回到我們最初,即使使用遞歸,要做到的也是每排擺放一個皇后

so

void slove(int row)    // 這裏改爲處理第row行皇后 

{

    // 這裏用來處理,第row行皇后分別放在col列上的結果

    for( int col = 0; col < 8; ++col )

    {

        // 這裏進行遞歸處理

    }

}

遞歸處理裏也是很簡單

// 這裏處理第row行第col列的皇后,是否可以擺放

void recursion(int row, int col)

{

    // 判斷是否與之前的皇后衝突

    if( !judge( row, col ) )

    {

        // 不發生衝突,將此位置記錄

        arr[row] = col;

        // 若達到第七行,表示一次擺放方法結束

        if ( row == 7 ) {

            disp();    // 輸出擺放結果

            return;

        }

        // 繼續遍歷下一個皇后位置

        slove( row + 1 );

    }

    

    // 發生衝突,不處理,退出此函數,會在slove函數中繼續處理後一列的擺放位置

}

最終slove方法稍加封裝可以這樣

void slove()
{
    slove_( 0 ); // 處理第1行
}

void slove_(int row)    
{
    for( int col = 0; col < 8; ++col )
    {
        recursion( row, col );
    }
}

void recursion(int row, int col)
{
    if( !judge( row, col ) )
    {
        arr[row] = col;
        if ( row == 7 ) {
            disp();
            return;
        }
      
        slove_( row + 1 );
    }
}



當然,這個,也可以稍加封裝成類,就不再浪費大家時間了

最後,我關於回溯的理解,就是

    循環+遞歸

好了,最後,拖一個以前寫的八皇后程序

這是基於Qt寫出來的demo程序,放出下載鏈,感興趣的可以看看

 http://download.csdn.net/detail/satanzw/4813268

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