回溯法解決N皇后問題(以四皇后爲例)

以4皇后爲例,其餘的N皇后問題以此類推。所謂4皇后問題就是求解如何在4×4的棋盤上無衝突的擺放4個皇后棋子。在國際象棋中,皇后的移動方式爲橫豎交叉的,所以在任意一個皇后所在位置的水平、豎直、以及45度斜線上都不能出現皇后的棋子,例子
圖片描述算法

要求編程求出符合要求的狀況的個數。四皇后問題有不少種解法,這裏主要介紹一種經典的解決方法:回溯法編程

回溯法的基本思想是:能夠構建出一棵解空間樹,經過探索這棵解空間樹,能夠獲得四皇后問題的一種或幾種解。這樣的解空間樹有四棵spa

在如上圖所示的4×4的棋盤上,按列來擺放棋子,首先由於皇后棋子不能在同一列,因此先排除有2個或2個以上的棋子在同一列的狀況,因此第一個棋子在第一列有4種擺放方法(第1列第1行,第1列第2行,第1列第3行,第1列第4行),一樣第二個棋子在第二列有4種,一樣第三個棋子在第三列有4種,一樣第四個棋子在第四列有4種,因此進行簡單的排除不在同一列的狀況後,還有4×4×4×4=256種可能,可是在這256種可能裏,依然存在好比棋子在同一行,或在45度斜線上的狀況出現。另外一個角度思考,全部的知足四皇后問題的擺放方式必定都存在於這256種狀況之中。簡單的理解就是:這256種棋盤局面包含了全部知足4皇后問題的解,可是不包含所有的棋盤局面。code

下面是解空間樹的示例(以上一段的按列擺放的方式來進行示例講解),其中第i層的棋盤局面是在第i-1層的棋盤局面演化而來的(1<i<4)
圖片描述遞歸

上面的圖片是以第一個棋子在第一列的第一行而派生出的一個解空間樹,最後一層會有64中結局面,同理在以第一個棋子在第1、列的第二/三/四行都分別能夠派生出一個解空間樹,最後一層都會有64中局面,因此有4棵解空間樹,每一棵最終有64個局面,因此一共有4×64=256種局面圖片

能夠用上面的方法窮舉出全部的解,再遍歷窮舉的全部結果找出全部符合四皇后問題的解,可是這樣會很浪費。因此這裏能夠用到回溯法,在構建解空間樹的途中進行深度優先探索,當探索到某一種棋盤局面必定不是四皇后問題的解的時候(好比出現任意兩個或兩個以上的棋子在同一行/同一列/45度斜線上),就能夠判斷這個節點向下派生出的解空間樹的節點也必定不是四皇后問題的解,這樣就能夠避免大量的無用功。資源

好比上圖中第二行的第一個節點出現了兩個棋子在同一行的狀況,因此能夠判斷出這個節點以及這個節點向下派生出的全部節點就再也不有必要進行遍歷了,這樣就會避免4+4×4次的徹底無用功的遍歷,就會大大的節省時間,再去探索第二行的第二個節點……其餘的同理。it

這樣,若是可以成功遍歷到葉子節點,而且判斷該葉子節點的局面就是符合4皇后問題的,那麼這個節點局面就表明一個合法的四皇后問題的解。下面的圖片就表明找到的一個合法的解的過程(注意圖片中,虛線表明排除,黑實線表明繼續向下探索) 以上圖爲例,當在第i層出現非法的棋盤局面時,就跳回第i-1層,繼續探索第i-1層的那個節點的下一個分支;或者在第4層探索到合法的局面就進行記錄並跳回上一層,繼續探索下一個分支。其餘三個解空間樹同理。
圖片描述io

以上圖爲例,就單看探索的第四層節點的個數。使用回溯法,就只需探索第4層中的4個節點,而若是使用窮舉法,就要探索玩第4層的全部64個節點,顯而易見,哪個方法更有效。class

其實在解決四皇后問題的時候,並不必定要真的構建出這樣的一棵解空間樹,它徹底能夠經過一個遞歸回溯來模擬。所謂的解空間樹只是一個邏輯上的抽象。固然也能夠用樹結構來真實的建立出一棵解空間樹,不過那樣會比較浪費空間資源,也沒有那個必要

解決四皇后問題的算法描述以下

#include<stdio.h>

int count = 0;
int isCorrect(int i, int j, int (*Q)[4])
{
    int s, t;
    for(s=i,t=0; t<4; t++)
        if(Q[s][t]==1 && t!=j)
            return 0;//判斷行
    for(t=j,s=0; s<4; s++)
        if(Q[s][t]==1 && s!=i)
            return 0;//判斷列
    for(s=i-1,t=j-1; s>=0&&t>=0; s--,t--)
        if(Q[s][t]==1)
            return 0;//判斷左上方
    for(s=i+1,t=j+1; s<4&&t<4;s++,t++)
        if(Q[s][t]==1)
            return 0;//判斷右下方
    for(s=i-1,t=j+1; s>=0&&t<4; s--,t++)
        if(Q[s][t]==1)
            return 0;//判斷右上方
    for(s=i+1,t=j-1; s<4&&t>=0; s++,t--)
        if(Q[s][t]==1)
            return 0;//判斷左下方

    return 1;//不然返回
}

void Queue(int j, int (*Q)[4])
{
    int i,k;
    if(j==4){//遞歸結束條件
        for(i=0; i<4; i++){
                //獲得一個解,在屏幕上顯示
            for(k=0; k<4; k++)
                printf("%d ", Q[i][k]);
            printf("\n");
        }
        printf("\n");
        count++;
        return ;
    }
    for(i=0; i<4; i++){
        if(isCorrect(i, j, Q)){//若是Q[i][j]能夠放置皇后
            Q[i][j]=1;//放置皇后
            Queue(j+1, Q);//遞歸深度優先搜索解空間樹
            Q[i][j]=0;//這句代碼就是實現回溯到上一層
        }
    }
}

int main()
{
    int Q[4][4];
    int i, j;
    for(i=0; i<4; i++)
        for(j=0; j<4; j++)
            Q[i][j] = 0;
    Queue(0, Q);
    printf("The number of the answers are %d\n", count);
    return 0;
}
相關文章
相關標籤/搜索