一直知道有个N皇后问题(皇后是国际象棋里棋子),不过一看见国际象棋这四个字,就觉得这个问题比较麻烦,所以以前也从来没看过N皇后问题是什么。昨天有精神看了一下该问题,题目也十分简单,突然觉得很有意思,遂把它解了出来。以下是自己的思路和代码:
N皇后描述:
N皇后问题就是说在一个N*N得方格矩阵中,新的皇后必须放入一个它不会被吃掉的方格内,(所谓吃就是国际象棋中皇后的规则,可以在一条直线内随便移动,包括行,列,斜线)所以从第一个皇后放入开始,它都要标记那些地方时不能放置元素的。
N皇后问题以能够放置N个皇后而结束,(即每列一个皇后),每放入一个皇后,都要将其压栈,如果第col列没有合适位置供新的皇后放入(即该列已经从第一个元素遍历到最后一个元素,仍未找到合适位置),则应该将栈顶皇后弹出(eg:皇后的行为i,列为j),从j列i+1行从继续进行下一次探索。
要列出所有的N皇后的答案,则需使用递归。
N皇后思路:
首先定义吃的规则,即皇后放入之后都要定义那些位置是不能放置的。我的想法是做标记,一旦该皇后要从栈顶弹出,则还要相应的撤销标记。标记可以考虑使用C的位运算,(我个人是比较喜欢位运算的)所有空格初始为0,每插入一个皇后,都要在其势力范围内置标记位,比如:插入第一个元素,则让其势力范围内的所有空格|1,插入第2个元素,让其势力范围内所有空格|(1<<1),如果是N皇后,让其势力范围所有空格|(1<<N-1),当改皇后从栈顶弹出式,要撤销标记位,此时让其势力范围内所有空格进行相应的补码计算即可
当然吃的规则可以根据个人习惯有不同的定义,我也是突发奇想,觉得这样做比较有趣,也比较直观。
N皇后算法:
1、检查是否有空余的列。没有,转3。有则检查该列是否有空格能放入。能放入,转2。不能,转4
2、设置相应空格位置状态,将皇后压栈。转1
3、每一列都有一个皇后,则说明成功
4、当前列找不到合适位置放置一个新的皇后,则弹出栈顶皇后,并将其所对应的空格标记取消,回到上一列的下一个位置重新开始1
代码如下:
-------------------------------------------------------------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 32
#define BITS 32
int m[MAXSIZE][MAXSIZE];
typedef struct blank
{
int col;
int row;
}Blank;
typedef struct stack
{
Blank elem[MAXSIZE];
int top;
}Stack;
InitStack(Stack **s)
{
*s = (Stack *)malloc(sizeof(struct stack));
(*s)->top = 0;
}
int StackEmpty(Stack *s)
{
return s->top == 0;
}
int Push(Stack *S, Blank *B)
{
if(S->top >= MAXSIZE)
return -1;
else
{
S->elem[S->top] = *B;
S->top++;
}
return 0;
}
int Pop(Stack *S,Blank *B)
{
if(S->top == 0)
return -1;
else
*B = S->elem[--S->top];
return 0;
}
/*将置标志位写为Light(亮灯),应该更有趣吧*/
void Light(int n,int x,int y)
{
int i,j;
int t = 1<<y;
i = x;j = y;
while(i <n && j<n)
{
i++;j++;(m[i][j]) |= t;
}
i = x;j = y;
while(i>0 && j >0)
{
i--;j--;(m[i][j]) |= t;
}
i = x;j = y;
while(i>0 && j<n)
{
i--;j++;(m[i][j]) |= t;
}
i = x;j = y;
while(i<n && j>0)
{
i++;j--;(m[i][j]) |= t;
}
for(j = 0;j<n;j++)
(m[x][j]) |= t;
for(i = 0;i<n;i++)
(m[i][y]) |= t;
}
/*将去标志位改成Darker(变暗)也应该很有趣吧*/
void Darker(int n,int x,int y)
{
int i,j;
int t = (1<<BITS)-1-(1<<y); /*补码*/
i = x;j = y;
while(i <n && j<n)
{
i++;j++;(m[i][j]) &= t;
}
i = x;j = y;
while(i>0 && j >0)
{
i--;j--;(m[i][j]) &= t;
}
i = x;j = y;
while(i>0 && j<n)
{
i--;j++;(m[i][j]) &= t;
}
i = x;j = y;
while(i<n && j>0)
{
i++;j--;(m[i][j]) &= t;
}
for(j = 0;j<n;j++)
(m[x][j]) &= t;
for(i = 0;i<n;i++)
(m[i][y]) &= t;
}
InitBlank(Blank **B)
{
(*B) = (Blank *)malloc(sizeof (struct blank));
(*B)->col = 0;
(*B)->row = 0;
}
DispStack(Stack *s)
{
int i;
Blank *B ;
InitBlank(&B);
for(i = 1; i<=s->top;i++)
{
*B = s->elem[(s->top)-i];
printf("m[%d][%d]/n",B->row,B->col);
}
}
Blank *CB(int col,int row)
{
Blank *B;
B = (Blank *)malloc(sizeof(Blank));
B->col = col;
B->row = row;
return B;
}
/*以下是问题核心*/
void Queen(int n,int r,int c)
{
Stack *s;
Blank *B;
int col,row;
InitStack(&s);
InitBlank(&B);
col = c;
row = r;
while(col < n)
{
while(row < n)
{
if(m[row][col] == 0) /*说明m[col][row]中值未曾改变,可以放置*/
{
Push(s,CB(col,row)); /*把该皇后(i,j)压栈*/
Light(n,row,col);/*修改势力范围,即同行,同列,左斜,右斜都设置标志位*/
col++;
row = 0;
break;
}else
row ++;
}
if(row > n-1 && col < n)
{
if(col == 0)/*已经是第一列,并且是最后一个元素已经尝试,则跳出*/
break;
else
{
Pop(s,B); /*如果列还没遍历到第n个,所有的行已经不能放置,则表示应该回溯*/
Darker(n,B->row,B->col);/*将该皇后弹出后,修改其改变过的标志位*/
col = B->col;
row = B->row+1; /* 返回上一列的下一个位置继续寻找*/
}
}
}
if(col > n-1)//说明已经找到符合条件的一组数据
{
DispStack(s);
printf("/n");
while(!StackEmpty(s))
{
Pop(s,B);
Darker(n,B->row,B->col);
}
col = 0;
row = B->row+1;
Queen(n,row,col);
}
}
int main(void)
{
int col,row;
int n ;
printf("输入数组维数(不能大于32)/n");
scanf("%d",&n);
for(col = 0;col < n;col++)
for(row = 0;row < n; row++)
m[col][row] = 0;
Queen(n,0,0);
return 0;
}
这个解法只是自己的一个思路的实现,远谈不上理想,以后有机会还会补充。