問題描述
會下國際象棋的人都很清楚:皇后可以在橫、豎、斜線上不限步數地吃掉其他棋子。如何將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的皇后串
輸入樣例
2
1
92
輸出樣例
15863724
84136275
解題思路一
因爲要求出92種不同擺放方法中的任意一種,所以我們不妨把92種不同的擺放方法一次性求出來,存放在一個數組裏。爲求解這道題我們需要有一個矩陣仿真棋盤,每次試放一個棋子時只能放在尚未被控制的格子上,一旦放置了一個新棋子,就在它所能控制的所有位置上設置標記,如此下去把八個棋子放好。當完成一種擺放時,就要嘗試下一種。若要按照字典序將可行的擺放方法記錄下來,就要按照一定的順序進行嘗試。也就是將第一個棋子按照從小到大的順序嘗試;對於第一個棋子的每一個位置,將第二個棋子從可行的位置從小到大的順序嘗試;在第一第二個棋子固定的情況下,將第三個棋子從可行的位置從小到大的順序嘗試;依次類推。首先,我們有一個8*8的矩陣仿真棋盤標識當前已經擺放好的棋子所控制的區域。用一個有92行每行8個元素的二維數組記錄可行的擺放方法。用一個遞歸程序來實現嘗試擺放的過程。基本思想是假設我們將第一個棋子擺好,並設置了它所控制的區域,則這個問題變成了一個7皇后問題,用與8皇后同樣的方法可以獲得問題的解。那我們就把重心放在如何擺放一個皇后棋子上,擺放的基本步驟是:從第1到第8個位置,順序地嘗試將棋子放置在每一個未被控制的位置上,設置該棋子所控制的格子,將問題變爲更小規模的問題向下遞歸,需要注意的是每次嘗試一個新的未被控制的位置前,要將上一次嘗試的位置所控制的格子復原。
參考程序一
- #include <stdio.h>
- #include <math.h>
- int queenPlaces[92][8]; //存放92種皇后棋子的擺放方法
- 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); //從第0個棋子開始擺放,運行的結果是將queenPlaces生成好
- 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;
- //將其後所有的擺放方法的第ith個皇后都放在i+1的位置上
- //在i增加以後,後面的第ith個皇后擺放方法後覆蓋此時的設置
- 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; //record記錄全部解,mark記錄當前解;
bool range[9], line1[17], line2[17]; //分別記錄列方向,45度,135度方向上被控制的情況
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++){ //將當前皇后i逐一嘗試放置在不同的列
for(k=0; k<i; k++) //逐一判定i與前面的皇后是否衝突
if( hang[k] == j || (k - i) == (hang[k] - j) || (i - k) == (hang[k] - j )) break;
if (k == i) { //放置i,嘗試第i+1個皇后
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位的十進制整數來完成輸出,形成不必要的冗餘代碼。