問題描述
會下國際象棋的人都很清楚:皇后可以在橫、豎、斜線上不限步數地吃掉其他棋子。如何將8個皇后放在棋盤上(有8 * 8個方格),使它們誰也不能被吃掉!這就是著名的八皇后問題。 對於某個滿足要求的8皇后的擺放方法,定義一個皇后串a與之對應,即a=b1b2...b8,其中bi爲相應擺法中第i行皇后所處的列數。已經知道8皇后問題一共有92組解(即92個不同的皇后串)。給出一個數b,要求輸出第b個串。串的比較是這樣的:皇后串x置於皇后串y之前,當且僅當將x視爲整數時比y小。 輸入數據第1行是測試數據的組數n,後面跟着n行輸入。每組測試數據佔1行,包括一個正整數b(1 <= b <= 92)輸出要求n行,每行輸出對應一個輸入。輸出應是一個正整數,是對應於b的皇后串輸入樣例2192輸出樣例1586372484136275
解題思路一
因爲要求出92種不同擺放方法中的任意一種,所以我們不妨把92種不同的擺放方法一次性求出來,存放在一個數組裏。爲求解這道題我們需要有一個矩陣仿真棋盤,每次試放一個棋子時只能放在尚未被控制的格子上,一旦放置了一個新棋子,就在它所能控制的所有位置上設置標記,如此下去把八個棋子放好。當完成一種擺放時,就要嘗試下一種。若要按照字典序將可行的擺放方法記錄下來,就要按照一定的順序進行嘗試。也就是將第一個棋子按照從小到大的順序嘗試;對於第一個棋子的每一個位置,將第二個棋子從可行的位置從小到大的順序嘗試;在第一第二個棋子固定的情況下,將第三個棋子從可行的位置從小到大的順序嘗試;依次類推。首先,我們有一個8*8的矩陣仿真棋盤標識當前已經擺放好的棋子所控制的區域。用一個有92行每行8個元素的二維數組記錄可行的擺放方法。用一個遞歸程序來實現嘗試擺放的過程。基本思想是假設我們將第一個棋子擺好,並設置了它所控制的區域,則這個問題變成了一個7皇后問題,用與8皇后同樣的方法可以獲得問題的解。那我們就把重心放在如何擺放一個皇后棋子上,擺放的基本步驟是:從第1到第8個位置,順序地嘗試將棋子放置在每一個未被控制的位置上,設置該棋子所控制的格子,將問題變爲更小規模的問題向下遞歸,需要注意的是每次嘗試一個新的未被控制的位置前,要將上一次嘗試的位置所控制的格子復原。
參考程序一
-
#include <stdio.h>
-
#include <math.h>
-
int queenPlaces[92][8];
-
int count = 0;
-
int board[8][8];
-
void putQueen(int ithQueen);
-
-
void main()
-
{
-
int n, i, j;
-
for(i = 0; i < 8; i++){
-
for(j = 0; j < 8; j++)
-
board[i][j] = -1;
-
for(j = 0; j < 92; j++)
-
queenPlaces[j][i] = 0;
-
}
-
putQueen(0);
-
scanf("%d", &n);
-
for(i = 0; i < n; i++){
-
int ith;
-
scanf("%d", &ith);
-
for(j = 0; j < 8; j++)
-
printf("%d", queenPlaces[ith - 1][j]);
-
printf("\n");
-
}
-
}
-
void putQueen(int ithQueen){
-
int i, k, r;
-
if(ithQueen == 8){
-
count ++;
-
return;
-
}
-
for(i = 0; i < 8; i++){
-
if(board[i][ithQueen] == -1){
-
-
board[i][ithQueen] = ithQueen;
-
-
-
for(k = count; k < 92; k++)
-
queenPlaces[k][ithQueen] = i + 1;
-
-
for(k = 0; k < 8; k++)
-
for(r = 0; r < 8; r++)
-
if(board[k][r] == -1 &&
-
(k == i || r == ithQueen || abs(k - i) == abs(r - ithQueen)))
-
board[k][r] = ithQueen;
-
-
putQueen(ithQueen + 1);
-
-
for(k = 0; k < 8; k++)
-
for(r = 0; r < 8; r++)
-
if(board[k][r] == ithQueen) board[k][r] = -1;
-
}
-
}
-
}
解題思路二
上面的方法用一個二維數組來記錄棋盤被已經放置的棋子的控制情況,每次有新的棋子放置時用了枚舉法來判斷它控制的範圍。還可以用三個一維數組來分別記錄每一列,每個45度的斜線和每個135度的斜線上是否已經被已放置的棋子控制,這樣每次有新的棋子放置時,不必再搜索它的控制範圍,可以直接通過三個一維數組判斷它是否與已經放置的棋子衝突,在不衝突的情況下,也可以通過分別設置三個一維數組的相應的值,來記錄新棋子的控制範圍。
參考程序二
-
#include <stdio.h>
-
int record[92][9], mark[9], count = 0;
-
bool range[9], line1[17], line2[17];
-
void tryToPut(int );
-
void main()
-
{
-
int i, testtimes, num;
-
scanf("%d", &testtimes);
-
-
for(i = 0; i <=8; i++)
-
range[i] = true;
-
for(i = 0; i < 17; i ++)
-
line1[i] = line2[i] = true;
-
-
tryToPut(1);
-
-
while(testtimes --){
-
scanf("%d", &num);
-
for(i = 1; i <=8; i++)
-
printf("%d", record[num - 1][i]);
-
printf("\n");
-
}
-
}
-
-
void tryToPut(int i){
-
if(i > 8){
-
for(int k = 1; k < 9; k ++)
-
record[count][k] = mark[k];
-
count ++;
-
}
-
for(int j=1; j<=8; j++){ 逐一嘗試將當前皇后放置在不同列上
-
if(range[j] && line1 [i + j] && line2[i - j + 9]){
-
-
mark[i] = j;
-
range[j] = line1[i + j] = line2[i - j + 9] = false;
-
tryToPut(i + 1);
-
range[j] = line1[i + j] = line2[i - j + 9] = true;
-
}
-
}
-
}
解題思路三
這個題目也可以不用仿真棋盤來模擬已放置棋子的控制區域,而只用一個有8個元素的數組記錄已經擺放的棋子擺在什麼位置,當要放置一個新的棋子時,只需要判斷它與已經放置的棋子之間是否衝突就行了。
參考程序三
-
#include <stdio.h>
-
int ans[92][8], n, b, i, j, num, hang[8];
-
void queen(int i){
-
int j, k;
-
if(i == 8){
-
for(j = 0; j < 8; j++) ans[num][j] = hang[j] + 1;
-
num++;
-
return;
-
}
-
for (j=0; j<8; j++){
-
for(k=0; k<i; k++)
-
if( hang[k] == j || (k - i) == (hang[k] - j) || (i - k) == (hang[k] - j )) break;
-
if (k == i) {
-
hang[i] = j;
-
queen(i + 1);
-
}
-
}
-
}
-
void main( ){
-
num=0;
-
queen(0);
-
scanf(“%d”, &n);
-
for(i = 0; i < n; i++){
-
scanf(“%d”, &b);
-
for(j = 0; j < 8; j++) printf(“%d”, ans[b - 1][j]);
-
printf(“\n”);
-
}
-
}
實現中常見的問題
問題一: 使用枚舉法,窮舉8個皇后的所有可能位置組合,逐一判斷是否可以互相被吃掉,得到超時錯誤;問題二:對於多組輸入,有多組輸出,沒有在每組輸出後加換行符,得到格式錯;問題三:對輸入輸出的函數不熟悉,試圖將數字轉換成字符或者將8個整數轉換成8位的十進制整數來完成輸出,形成不必要的冗餘代碼。