1. 程式人生 > >回溯法(八皇后問題)及C語言實現

回溯法(八皇后問題)及C語言實現

       回溯法,又被稱為“試探法”。解決問題時,每進行一步,都是抱著試試看的態度,如果發現當前選擇並不是最好的,或者這麼走下去肯定達不到目標,立刻做回退操作重新選擇。這種走不通就回退再走的方法就是回溯法。

回溯VS遞迴

很多人認為回溯和遞迴是一樣的,其實不然。在回溯法中可以看到有遞迴的身影,但是兩者是有區別的。 回溯法從問題本身出發,尋找可能實現的所有情況。和窮舉法的思想相近,不同在於窮舉法是將所有的情況都列舉出來以後再一一篩選,而回溯法在列舉過程如果發現當前情況根本不可能存在,就停止後續的所有工作,返回上一步進行新的嘗試。 遞迴是從問題的結果出發,例如求 n!,要想知道 n!的結果,就需要知道 n*(n-1)! 的結果,而要想知道 (n-1)! 結果,就需要提前知道 (n-1)*(n-2)!。這樣不斷地向自己提問,不斷地呼叫自己的思想就是遞迴。 回溯和遞迴唯一的聯絡就是,回溯法可以用遞迴思想實現。

回溯法與樹的遍歷

使用回溯法解決問題的過程,實際上是建立一棵“狀態樹”的過程。例如,在解決列舉集合{1,2,3}所有子集的問題中,對於每個元素,都有兩種狀態,取還是舍,所以構建的狀態樹為:

圖1 狀態樹

回溯法的求解過程實質上是先序遍歷“狀態樹”的過程。樹中每一個葉子結點,都有可能是問題的答案。圖 1 中的狀態樹是滿二叉樹,得到的葉子結點全部都是問題的解。 在某些情況下,回溯法解決問題的過程中建立的狀態樹並不都是滿二叉樹,因為在試探的過程中,有時會發現此種情況下,再往下進行沒有意義,所以會放棄這條死路,回溯到上一步。在樹中的體現,就是在樹的最後一層不是滿的,即不是滿二叉樹,需要自己判斷哪些葉子結點代表的是正確的結果。

回溯法解決八皇后問題

八皇后問題是以國際象棋為背景的問題:有八個皇后(可以當成八個棋子),如何在 8*8 的棋盤中放置八個皇后,使得任意兩個皇后都不在同一條橫線、縱線或者斜線上。

                                                                            圖 2 八皇后問題示例(#代表皇后)

八皇后問題是使用回溯法解決的典型案例。演算法的解決思路是:

  1. 從棋盤的第一行開始,從第一個位置開始,依次判斷當前位置是否能夠放置皇后,判斷的依據為:同該行之前的所有行中皇后的所在位置進行比較,如果在同一列,或者在同一條斜線上(斜線有兩條,為正方形的兩個對角線),都不符合要求,繼續檢驗後序的位置。
  2. 如果該行所有位置都不符合要求,則回溯到前一行,改變皇后的位置,繼續試探。
  3. 如果試探到最後一行,所有皇后擺放完畢,則直接打印出 8*8 的棋盤。最後一定要記得將棋盤恢復原樣,避免影響下一次擺放。 

原始碼:

#include <stdio.h>
int Queenes[8]={0},Counts=0;
int Check(int line,int list){
    //遍歷該行之前的所有行
    for (int index=0; index<line; index++) {
        //挨個取出前面行中皇后所在位置的列座標
        int data=Queenes[index];
        //如果在同一列,該位置不能放
        if (list==data) {
            return 0;
        }
        //如果當前位置的斜上方有皇后,在一條斜線上,也不行
        if ((index+data)==(line+list)) {
            return 0;
        }
        //如果當前位置的斜下方有皇后,在一條斜線上,也不行
        if ((index-data)==(line-list)) {
            return 0;
        }
    }
    //如果以上情況都不是,當前位置就可以放皇后
    return 1;
}
//輸出語句
void print()
{
    for (int line = 0; line < 8; line++)
    {
        int list;
        for (list = 0; list < Queenes[line]; list++)
            printf("0");
        printf("#");
        for (list = Queenes[line] + 1; list < 8; list++){
            printf("0");
        }
        printf("\n");
    }
    printf("================\n");
}
void eight_queen(int line){
    //在陣列中為0-7列
    for (int list=0; list<8; list++) {
        //對於固定的行列,檢查是否和之前的皇后位置衝突
        if (Check(line, list)) {
            //不衝突,以行為下標的陣列位置記錄列數
            Queenes[line]=list;
            //如果最後一樣也不衝突,證明為一個正確的擺法
            if (line==7) {
                //統計擺法的Counts加1
                Counts++;
                //輸出這個擺法
                print();
                //每次成功,都要將陣列重歸為0
                Queenes[line]=0;
                return;
            }
            //繼續判斷下一樣皇后的擺法,遞迴
            eight_queen(line+1);
            //不管成功失敗,該位置都要重新歸0,以便重複使用。
            Queenes[line]=0;
        }
    }
}
int main() {
    //呼叫回溯函式,引數0表示從棋盤的第一行開始判斷
    eight_queen(0);
    printf("擺放的方式有%d種",Counts);
    return 0;
}

因為八皇后擺放方式有92種,這裡也不再一一列舉。