這個問題還能夠加以衍伸,變成「在一個 n × n 的西洋棋盤上,使 n 個皇后全部放在棋盤上,而不能被其他皇后吃掉」的 n 皇后問題。
若是只需要輸出其中一種排法,我們可以使用遞迴求解。嘗試每一種棋子擺放的方式,再找出符合條件(皇后間無法互相攻擊)的擺法。上述的狀態空間樹(state space tree)如下圖(為簡化作圖,這裡的 n = 4):
[i, j] 代表第 i 行的皇后擺在第 j 列上。這棵樹的葉節點(leaf nodes),即為此問題所有可能解的集合。而上圖的狀態空間樹,總共有 256 (44) 個葉節點。
不過,這種方式還是相當沒效率,所以我們必須想辦法減少遞迴的次數。
首先我們知道,因為皇后可以橫向、直向與斜向移動,所以我們可以在遞迴前先判斷:現在這個皇后放置的位置,是否會與其他皇后衝突。上述的狀態空間樹如下圖所示:
跟前一張圖比較,節點 [1, 1] 之下的 [2, 1] 與 [2, 2] 因為會與 [1, 1] 衝突,所以遞迴時不會走訪這個節點。同理,節點 [2, 3] 之下的所有節點,因為會與 [1, 1] 或 [2, 3] 衝突,所以遞迴時也不會走訪這個節點。
如此就可以有效的減少遞迴的次數。這種方式就稱之為修剪(pruning)。
至於要如何判斷某位置是否會與其他皇后衝突呢?我的方式是使用分別作列、左斜與右斜的陣列。每放置一個皇后,就標記其相對應的三個變數值。
完整的虛擬碼大致如下:
void queens(Index x) { Index i; for (i = 1 to n) { Index j = i - x + n; Index k = x + i + 1; if (column[i] and right[j] and left[k]) { column[i] = right[j] = left[k] = false; queen(x + 1); column[i] = right[j] = left[k] = true; } } }
以下是使用 C 語言的實現:
#include <stdio.h> #include <stdlib.h> void queens(int x); void print(void); unsigned int n, num; char** checkerboard; int* column; int* right; int* left; int main(void) { int i, j; printf("請輸入 n 的大小: "); scanf("%d", &n); // 動態分配記憶體 column = malloc(sizeof(int) * n); right = malloc(sizeof(int) * (2 * n - 1)); left = malloc(sizeof(int) * (2 * n - 1)); checkerboard = malloc(sizeof(char*) * n); // 變數初始化 num = 0; for (i = 0; i < n; i++) { column[i] = 1; checkerboard[i] = malloc(sizeof(char) * n); for (j = 0; j < n; j++) { checkerboard[i][j] = 'x'; } } for (i = 0; i < 2 * n - 1; i++) { right[i] = left[i] = 1; } queens(0); printf("\n總共有 %d 種排法\n\n", num); // 釋放記憶體空間 for (i = 0; i < n; i++) { free(checkerboard[i]); } free(checkerboard); free(column); free(right); free(left); system("pause"); } void queens(int x) { if (x < n) { int i; for (i = 0; i < n; i++) { int j = i - x + n - 1; int k = i + x; if (column[i] && right[j] && left[k]) { // 標記皇后位置, 並遞迴放置下一個 column[i] = right[j] = left[k] = 0; checkerboard[x][i] = 'Q'; queens(x + 1); column[i] = right[j] = left[k] = 1; checkerboard[x][i] = 'x'; } } } else { // 輸出棋盤 print(); num++; } } void print(void) { int i, j; printf("\n"); for(i = 0; i < n; i++) { for(j = 0; j < n; j++) { printf("%c", checkerboard[i][j]); } printf("\n"); } }
0 回覆:
張貼留言