N皇后问题的一种方案

来源:互联网 发布:mac 10.8.2能玩魔兽 编辑:程序博客网 时间:2024/04/29 00:22

 一直知道有个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;
}

 

这个解法只是自己的一个思路的实现,远谈不上理想,以后有机会还会补充。

原创粉丝点击