Chinaunix首页 | 论坛 | 博客
  • 博客访问: 100805
  • 博文数量: 52
  • 博客积分: 2095
  • 博客等级: 大尉
  • 技术积分: 500
  • 用 户 组: 普通用户
  • 注册时间: 2008-04-08 13:29
文章分类

全部博文(52)

文章存档

2010年(1)

2009年(24)

2008年(27)

我的朋友

分类: C/C++

2009-07-30 10:58:07

这么经典的问题一直没认真思考,现在终于弄出来了,还费了点周折。
用二维数组表示棋盘,空的全部置0。大概思路是递归处理每一行,一开始可以从n=4来思考,简单点。放棋子到合法位置上(置1),非法的位置就是为2的位置,已经被判断是不能放的了;然后使这颗棋子位置对应的列和斜行成为不能放的位置(置2),然后处理下一行。这样到最后一行输出棋盘结果。
下面的程序思路清晰,结果没考虑周全,有错误

#include <stdio.h>
#include <stdlib.h>

#define N 4
int a[N][N]={{0}}; //0表示没有放棋但还可能放,1表示放下,2表示不能放

int jie=0;
int num_of_jie=0;

void queen(int row,int n) //处理某一行

{
    int i,j,k;

    if (jie &&(row==n)) { //输出

        for(i=0;i<n;i++) {
            for(j=0;j<n ;j++ )
                if(a[i][j]!=1)
                    printf("0");
                else
                    printf("1");
            printf("\n");
        }
        printf("\n");
        jie=0;
        num_of_jie++;
    }
    else
      for (i=0;i<n ;i++ )
      {
        if (a[row][i]==2) continue; //该位置不能放棋


        a[row][i]=1; //放棋

        if(row==n-1) //最后一行处理完成

            jie = 1;

        for (j=row+1;j<n ;j++ )
            a[j][i]=2; //放的棋子这一列不能再放

        for(j=1;(j<(n-i))&&(j<(n-row)) ;j++) //注意终止条件,棋子可以在对角线上也可能在下

            a[row+j][i+j]=2; //对的斜行也不能放

        for(k=1;(k<=i)&&(k<(n-row)) ;k++) //对的反斜行也不能放

            a[row+k][i-k]=2;

        queen(row+1,n); //递归处理下一行


        for(--j;j>0;j--) // 恢复放这颗棋以前的状态,继续考虑下一个i值的可能性,这里利用j先恢复斜行

            a[row+j][i+j]=0;
        for(--k;k>0;k--) //恢复反斜行

            a[row+k][i-k]=0;
        for (j=n-1;j>=row ;j-- ) //恢复列,和这次放棋的位置

            a[j][i]=0;

     }
}


int main()
{

    queen(0,N);
    printf("\n共有%d组解\n",num_of_jie);
    return 0;
}

错就错在没有考虑这一个事实:递归后,恢复当前棋子的影响。有的位置在这粒棋子之前,就已经被判定为非法位置了(受前面某行放棋的影响),却恢复成了合法位置。于是程序会可能在这一本来是非法的位置上放棋,最终导致输出错误结果。这个错误程序在八皇后时输出了229个解,明显是不对的。
这使我知道了,每次恢复放棋所影响的位置为0,应该是只有该颗棋子影响的位置才恢复,之前棋子同样影响的位置是应该保留的。怎么实现呢?我用一个数组模拟栈,先将受影响的棋子的坐标放进去,后面再恢复,这样就可以通过判断来保证达到上面所说。修改后的程序如下

#include <stdio.h>
#include <stdlib.h>

#define N 8
int a[N][N]={{0}}; //0表示没有放棋但还可能放,1表示放下,2表示不能放

int jie=0; //有解的标志

int num_of_jie=0;

void queen(int row,int n) //处理某一行

{
    int i,j,k;
    int stack[4*N]={0}; //利用一个数组作为栈,存储和恢复不可以放棋的位置

    int stackindex;
    int tempx,tempy;

    if (jie &&(row==n)) { //输出

        for(i=0;i<n;i++) {
            for(j=0;j<n ;j++ )
                if(a[i][j]!=1)
                    printf("0");
                else
                    printf("1");
            printf("\n");
        }
        printf("\n");
        jie=0;
        num_of_jie++;
    }
    else
      for (i=0;i<n ;i++ )
      {
        if (a[row][i]==2) continue; //该位置不能放棋


        a[row][i]=1; //放棋

        if(row==n-1) //最后一行处理完成

            jie = 1;
        stackindex=0;
        for (j=row+1;j<n ;j++ )
            if(a[j][i]!=2) { //如果等于2,表示之前的棋子已经使这里不能放棋了,属于前面旗子的处理范围,不过问

                a[j][i]=2;
                stack[stackindex++]=j; //记录横坐标

                stack[stackindex++]=i; //记录纵坐标

            }
            else
                continue;
        for(j=1;(j<(n-i))&&(j<(n-row)) ;j++) //对的斜行也不能放。注意终止条件,棋子可以在对角线上也可能在下

            if(a[row+j][i+j]!=2) {
                a[row+j][i+j]=2;
                stack[stackindex++]=row+j;
                stack[stackindex++]=i+j;
            }
            else
                continue;
        for(k=1;(k<=i)&&(k<(n-row)) ;k++) //对的反斜行也不能放

            if(a[row+k][i-k]!=2) {
                a[row+k][i-k]=2;
                stack[stackindex++]=row+k;
                stack[stackindex++]=i-k;
            }
            else
                continue;

        queen(row+1,n); //递归处理下一行


        while(stackindex>0) { //恢复这颗棋子影响的位置,让它们又能放棋。

            tempy=stack[--stackindex];
            tempx=stack[--stackindex];
            a[tempx][tempy]=0;
        }
        a[row][i]=0; //恢复这颗棋子的位置为空


     }
}


int main()
{

    queen(0,N);
    printf("\n共有%d组解\n",num_of_jie);
    return 0;
}

阅读(352) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~