回溯思想——八皇后問題
首先描述一下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);
}