1. 程式人生 > >回溯思想——八皇后問題

回溯思想——八皇后問題

首先描述一下8皇后問題:

        在8x8的國際象棋棋盤上,放上八個皇后,使得每一個皇后都不能直接吃掉其他的皇后。在國際象棋中,皇后可以吃掉同行、列、45°線、135°線上的其他子。

介紹一下回溯法:

        回溯法與窮舉法非常相似,只是通過“剪枝”的思想,減少窮舉的次數。可以將回溯法理解為一種加強版的窮舉法。

        首先來介紹一下窮舉法解八皇后問題,可以這樣想:任意兩個皇后不能放在同一行,故8個皇后一定放在不同的8行中,每個皇后放的時候又有8個位置可以選擇,所以窮舉法應該是這樣的邏輯:

for  1  ---->   8                                 //放第1行
    for  1  ---->   8                             //放第2行
        for  1  ---->   8                         //放第3行
            for  1  ---->   8                     //放第4行
                for  1  ---->   8                 //放第5行
                    for  1  ---->   8             //放第6行
                        for  1  ---->   8         //放第7行
                            for  1  ---->   8     //放第8行
                                judge_quene()     //判斷擺放合法

        共有8^8種情況,這顯然有些多。仔細分析如果第1次和第2次皇后分別放在瞭如圖紅色、藍色的位置:

                

        這個時候已經違背了放置規則,那麼餘下6行的8^6種情況就一定都是不符合要求的,這就大大浪費了計算資源。合理的做法應該是這樣:當藍色的皇后放下時,判斷一下當前位置是否合理,不合理則將這種情況對應的後6行的操作都放棄,將藍色皇后從棋盤中拿出,繼續流程(放到黃色位置)。

        這就是回溯法的主要思想——在每次操作後對本次操作進行一次判斷,當本次操作已經違背了規則,則本次操作所對應的之後的操作就全都放棄不要了(這個過程稱為“剪枝”)。當完成了一條“枝”的全部可行情況的遍歷後,把本“枝”去掉(這個過程稱為“回溯”),換下一個條“枝”繼續。

        有了回溯思想,就可以把程式的邏輯變成這個樣子:

            

        演算法上使用遞迴法是最方便的,遞迴的跳出條件是:未被“剪枝”的行完成了1至8列的遍歷。

        記錄時使用:

                char quene_array[8];

        矩陣的行號代表棋盤的行號,矩陣的值代表對應行中皇后被放置在哪個列。遞迴函式如下:

void quene(char line, char * quene_array)
{
    int i;
    for (i = 0; i < 8; i++) {
        if (put_quene_judge(quene_array, line, i)) {
            quene_array[line] = i;                          //合法放置
            line++;
        }
        else {
            continue;                                       //剪枝動作
        }
        if (line == 8) {                                    //第8行完成了合法放置輸出擺放方式
            num++;
            printf("The %d way is:\n", num);
            print_quene(quene_array);
        }
        else {
            quene(line, quene_array);                       //合法放置則繼續遞迴
            line--;                                         //回溯動作
        }
    }
}

        最後介紹一下襬放合法性的判斷函式。判斷時遍歷放置的所有皇后,假設已放置的皇后行號和列號為(a1,b1),本次放置的皇后行列號為(a2,b2),則需要滿足:

        1、b1 != b2

        2、b1 - b2 != a1 - a2 45°對角線情況

        3、b1 - b2 != a2 - a1 125°對角線情況

判斷程式片段如下:

	
//皇后放置合法性判斷函式
char put_quene_judge(char *quene_array, char line, char num)
{
    int i;
    for (i = 0; i < line; i++) {
        if ((quene_array[i] == num) 
            || (line - i == num - quene_array[i]) 
            || (line - i == quene_array[i] - num))
            return 0;
    }
    return 1;
}

總結:

        本文通過八皇后的例子,說明了使用“剪枝”的思想,將窮舉法升級為回溯法。在窮舉例項數量很多,但符合條件例項卻很少時,往往會使用回溯法。

附上完整的c語言程式:

#include

int num;        //全域性變數,累加記錄有幾種可行的放置方法

//皇后位置列印
void print_quene(char *quene_array)
{
    int i, j;
    for (i = 0; i < 8; i++) {
        for (j = 0; j < 8; j++) {
            if (j == quene_array[i]) {
                printf(" O");
            }
            else {
                printf(" X");
            }
        }
        printf("\n");
    }
}

//皇后放置合法性判斷函式
char put_quene_judge(char *quene_array, char line, char num)
{
    int i;
    for (i = 0; i < line; i++) {
        if ((quene_array[i] == num) 
            || (line - i == num - quene_array[i]) 
            || (line - i == quene_array[i] - num))
            return 0;
    }
    return 1;
}

void quene(char line, char * quene_array)
{
    int i;
    for (i = 0; i < 8; i++) {
        if (put_quene_judge(quene_array, line, i)) {
            quene_array[line] = i;                          //合法放置
            line++;
        }
        else {
            continue;                                       //剪枝動作
        }
        if (line == 8) {                                    //第8行完成了合法放置輸出擺放方式
            num++;
            printf("The %d way is:\n", num);
            print_quene(quene_array);
        }
        else {
            quene(line, quene_array);                       //合法放置則繼續遞迴
            line--;                                         //回溯動作
        }
    }
}

void main()
{
    char quene_array[8];
    num = 0;
    quene(0, quene_array);
}