转载请注明出处,谢谢~
背景说明:
西洋棋中的皇后可以直线,斜线吃掉所遇到的棋子,如果在8*8的棋盘上有八个皇后,则这八个皇后如何相安无事的摆放在棋盘上?1970年与1971年,E.W.Dijkstra与N.Wirth曾经用这个问题来讲解程式设计之技巧。
大天朝白话文:
有8*8的棋盘,需要在这个棋盘上摆放8个皇后(相同横、竖、斜线上不能再有皇后),求一种有多少种不同 的摆法?
图片一摆,很容易想到二维数组解决问题,就是在遍历的过程中判断二维数组的值是不是等于我们设定的此处可以摆放皇后的值。但是这样光想想就知道代码会略微繁琐,因为我们每放置一个皇后就要更改同行同列同斜线上的值,那么有没有一种方式可以不需要改变如此多的值就能解决问题?找规律!
假设我们从第一行开始摆放皇后,也就是图中的最后一行,我们会发现我们并不需要判断哪一行可以放置皇后,因为我们会递归的以行为基数进行循环,第一行放完了当然就是第二行,在第二行我们只需要判断第二行中的哪一列能放置就行,同样在第三行只考虑哪一列能放置皇后,所以这就省去了行的判断,我们用一个一位数组表示列定义为column[ ],然后我们再来考虑斜线。
反斜线:
规律显而易见,一条斜线上的所有可摆放位置的坐标和值是相等的!即x+y=i+j;那么这条斜线上能不能放置皇后就是用一个和值就能判断,而不需要二维数组来更改值,我们用一个一维数组保存和值,和值为一维数组的下标。即rup【i+j】就是rup【1+8】,那么如果这个数组中的值不是可以放置皇后的值,就说明这条斜线上就不能放置皇后了。
正斜线:
同样规律显而易见,但是这里有个问题,会出现负值。有人说取绝对值就可以了,但是同样的绝对值也代表了另一条斜线,所以这个规律暂时舍弃,看看有么有其他的规律。
只要在后边加上一个8就可以,这样就保证值的正数和唯一性。然后同样定义一个数组lup表示正斜线,值存在对应的数组下标中。然后我们就可以构造代码了。
void backtrack(int i)
{
int j;
if(i > N)
showAnswer();
else
for (j=1;j<=N;j++)
{
if(column[j] == 1 && rup[i+j] == 1 &&lup[i-j+N] == 1){
queen[i] = j;
//设定为占用
column[j] = rup[i+j] = lup[i-j+N] = 0;
backtrack(i+1);
column[j] = rup[i+j] = lup[i-j+N] = 1; //在左下角递归完后进行下一个位置的递归运算
}
}
}
#include <stdio.h>
#include <stdlib.h>
#define N 8
int column[N+1];//同栏是否有皇后
int rup[2*N +1];//右上至左下是否有皇后
int lup[2*N +1];//左上至右下是否有皇后
int queen[N+1] = {0};
int num ;//解答编号
void backtrack(int);//递回求解
main(void)
{
int i;
num = 0;
for(i=1;i<=N;i++)
column[i] = 1;
for(i=1;i<=2*N;i++)
rup[i] = lup[i] = 1;
backtrack(1);
system("pause");
return 0;
}
void showAnswer()
{
int x,y;
printf("\n 解答 %d\n",++num);
for (y=1;y<=N;y++)
{
for (x=1;x<=N;x++)
{
if(queen[y] == x)
printf("Q");
else
printf("K");
}
printf("\n");
}
}
void backtrack(int i)
{
int j;
if(i > N)
showAnswer();
else
for (j=1;j<=N;j++)
{
if(column[j] == 1 && rup[i+j] == 1 &&lup[i-j+N] == 1){
queen[i] = j;
//设定为占用
column[j] = rup[i+j] = lup[i-j+N] = 0;
backtrack(i+1);
column[j] = rup[i+j] = lup[i-j+N] = 1; //在左下角递归完后进行下一个位置的递归运算
}
}
}
可以看到一共有92种结果,完美执行~!