遞歸回溯法解決八皇后問題
一、八皇后問題
皇后是國際象棋中威力最大的棋子。她可以攻擊同一行、同一列以及與她處在斜線上的棋子。八皇后問題在1848年由國際西洋棋棋手馬克斯·貝瑟爾提出:如何在8x8格的棋盤上擺放八個皇后,使她們不能互相攻擊?
上圖是92種擺法中的其中一種。
一個有用的經驗是:在正式敲程式碼之前,我們應該對程式的流程有清晰的理解。這意味著我需要先做出流程圖或者偽演算法。
另一個經驗是,使用函式將大問題分解成若干個小問題,可以極大地降低程式設計的難度和提高程式的可讀性。為了獲得更好的可讀性,犧牲一點效率是值得的。
下面,我會介紹我的思路,由簡入深地解決這個問題。
二、準備工作
首先,我定義了一個8*8的整型二維陣列,用來描述棋盤。我將它所有元素預設初始化為0,0代表某個位置沒有放皇后。1代表有皇后。一開始棋盤上什麼都沒有,所以全是0。
int chess[8][8] = { 0 };
接下來,我根據將來可能會用到的操作編寫了幾個函式。分別是:
1、在指定的位置放置一枚皇后
void place_chess(int (*m)[8], int x, int y) {
m[x][y] = 1;
}
2、拿走某個位置的皇后
void remove_chess(int (*m)[8], int x, int y) {
m[x][y] = 0 ;
}
3、列印棋盤
void print_chess(int(*m)[8]) {
printf("****************\n");
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
printf("%d ", m[i][j]);
}
printf("\n");
}
printf("****************\n\n");
}
4、判斷能否在某個位置放置皇后,如果可以就返回1,否則返回0
int judge(int(*m)[8], int x, int y) {
int judgement = 1; // 預設可行
int k1 = 1; // 斜率1
int k2 = -1; // 斜率2
int b1 = y - k1 * x; // 點斜式的常數項1
int b2 = y - k2 * x; // 點斜式的常數項2
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
// 行、列、斜線
if (i == x && m[i][j] == 1) {
judgement = 0;
return judgement;
}
if (j == y && m[i][j] == 1) {
judgement = 0;
return judgement;
}
if (j == k1 * i + b1 && m[i][j] == 1) {
judgement = 0;
return judgement;
}
if (j == k2 * i + b2 && m[i][j] == 1) {
judgement = 0;
return judgement;
}
}
}
return judgement;
}
有必要解釋最後一個函式judge
。這個函式的功能是這樣的:傳進代表棋盤的二維陣列,還有代表一個格子的 x,y 座標。然後判斷能否在這個座標放置皇后。顯然,只要同行、同列、以及過這個點的兩條斜率為1和-1的直線上沒有其他皇后就行了。點斜式的公式是y = kx + b
。斜率只能是1或-1,對應的常數項b分別解出來。就構成了兩條斜線的公式,應該很好理解。
三、偽演算法
因為我們要在每一行放一個皇后,而每行的操作實際上是差不多的,這種重複性是遞迴的第一個特徵。定義一個函式,它只關注當前的行。
void putQueenInRow(int(*m)[8], int row);
這個函式和它的名字一樣,嘗試在某行放置皇后。下面給出這個函式的偽演算法:
迴圈8次:嘗試在當前行(row)的每一列放置皇后
{
如果可以放置在這個位置
{
在這個位置放置皇后
如果當前位置不是最後一行
{
遞迴呼叫自己,進入下一行
遞迴返回後,把當前位置的皇后拿走,然後嘗試下一列
}
如果當前處在最後一行
{
將當前的棋盤打印出來,這樣就得到了一種擺法
拿走當前位置的皇后
}
}
}
請花一些時間理解偽演算法,這是整個程式的關鍵。
在偽演算法的基礎上,我添加了一個靜態變數count
,用於記錄擺法的序號,每列印一種擺法,就將count
增加1
下面是偽演算法的實現:
// 在指定行的每一列嘗試放皇后
void putQueenInRow(int(*m)[8], int row) {
static count = 0; // 計數君,每列印一種擺法就加1
for (int col = 0; col < 8; col++) {
if (judge(m, row, col) == 1) {
// 如果可以,就在這裡放一個皇后
place_chess(m, row, col);
if (row != 7) {
// 如果不是最後一行,就進入下一行
putQueenInRow(m, row + 1);
// 將原來的皇后拿走,然後嘗試下一列
remove_chess(m, row, col);
continue;
}
else {
count++;
printf("這是第%d種擺法\n", count);
print_chess(m); // 否則就列印棋盤
remove_chess(m, row, col); // 拿走當前位置的皇后
}
}
}
}
四、完整程式碼
#include <stdio.h>
void place_chess(int(*m)[8], int x, int y);
void remove_chess(int(*m)[8], int x, int y);
int judge(int(*m)[8], int x, int y);
void print_chess(int(*m)[8]);
void putQueenInRow(int(*m)[8], int row);
int main() {
int chess[8][8] = { 0 };
putQueenInRow(chess, 0); // 從第一行開始嘗試
return 0;
}
// 在指定位置放皇后
void place_chess(int(*m)[8], int x, int y) {
m[x][y] = 1;
}
// 移走指定位置的皇后
void remove_chess(int(*m)[8], int x, int y) {
m[x][y] = 0;
}
// 判斷是否能在某個格子放皇后
int judge(int(*m)[8], int x, int y) {
int judgement = 1; // 預設可行
int k1 = 1; // 斜率1
int k2 = -1; // 斜率2
int b1 = y - k1 * x; // 點斜式的常數項1
int b2 = y - k2 * x; // 點斜式的常數項2
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
// 行、列、斜線
if (i == x && m[i][j] == 1) {
judgement = 0;
return judgement;
}
if (j == y && m[i][j] == 1) {
judgement = 0;
return judgement;
}
if (j == k1 * i + b1 && m[i][j] == 1) {
judgement = 0;
return judgement;
}
if (j == k2 * i + b2 && m[i][j] == 1) {
judgement = 0;
return judgement;
}
}
}
return judgement;
}
// 列印棋盤
void print_chess(int(*m)[8]) {
printf("****************\n");
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
printf("%d ", m[i][j]);
}
printf("\n");
}
printf("****************\n\n");
}
// 在指定行的每一列嘗試放皇后
void putQueenInRow(int(*m)[8], int row) {
static count = 0; // 計數君,每列印一種擺法就加1
for (int col = 0; col < 8; col++) {
if (judge(m, row, col) == 1) {
// 如果可以,就在這裡放一個皇后
place_chess(m, row, col);
if (row != 7) {
// 如果不是最後一行,就進入下一行
putQueenInRow(m, row + 1);
// 將原來的皇后拿走,然後嘗試下一列
remove_chess(m, row, col);
continue;
}
else {
count++;
printf("這是第%d種擺法\n", count);
print_chess(m); // 否則就列印棋盤
remove_chess(m, row, col); // 拿走當前位置的皇后
}
}
}
}
五、總結
折騰了兩天,經歷了無數次失敗,最後成功執行的瞬間看著控制檯往下滾了半秒,一看果然是92個解,這種感覺真是爽啊(^▽^)
btw,皇后太多有時不見得是好事呀o( ̄︶ ̄)o