中国象棋的将帅问题

这是《编程之美》上的一道题目,描述如下,在只有双的将帅棋盘上,找出所有双方可以落子的位置(将帅不能碰面),但只能使用一个变量。

题目的逻辑很简单,思路很容易想出:
把棋盘的九个位置,设置为按照行优先的顺序排列的序号1-9,这样就能用序号对3求余表示其列数。

遍历A的位置
    遍历B的位置
        比较A的列号和B的列号是否相等
        如果是 输出A和B的位置序号

难点在于只能声明一个变量。
只用一个变量却要储存A和B两个点的位置,回顾一下学过的变量类型,
bool型只能有两种状态无法表示。
自然想到unsigned char的类型,8位,最大能表示255个信息,左右各四位分别都能最多表示16种状态。满足题目要求。
那么,问题在,我们如何能将一个unsigned char的左右各四位分别进行读写操作呢?
另外,对遍历用的for(;;)循环怎么保证只用一个变量呢?
自然想到了宏操作。

第一种解法如下:

#include <stdio.h>
#define Half_Length 4
#define MAX 255
#define LMAX (MAX<<Half_Length)
#define RMAX (MAX>>Half_Length)
#define GetL(i) ((LMAX&i)>>Half_Length)//获取左半信息
#define GetR(i) (RMAX&i)//获取右半信息
#define SetL(i,n) (i=GetR(i)^(n<<Half_Length))
#define SetR(i,n) (i=(GetL(i)<<Half_Length)^n)
#define MAXGrow 3

int main(int argc, char *argv[])
{
    unsigned char b;
    for(SetL(b,1);GetL(b)<=MAXGrow*MAXGrow;SetL(b,GetL(b)+1))
    {
        for(SetR(b,1);GetR(b)<=MAXGrow*MAXGrow;SetR(b,GetR(b)+1))
            if(GetL(b)%MAXGrow!=GetR(b)%MAXGrow)
                printf("A:%d B:%d\n",GetL(b),GetR(b));
    }
    return 0;
}

运行结果如图:
![这里写图片描述](http://img.blog.csdn.net/20160517225003960)

这个解法很巧妙的解决了一个变量同时存储两个位置信息。
但是,我又在书上看到了第二种解法。
解法如下:

#include <stdio.h> 
main(int argc, char *argv[])
{
    for(int i=81;i>0;i--)
    {
        if(i/9%3!=i%9%3)
            printf("A:%d B:%d\n",i/9+1,i%9+1);
        }
    return 0;
}

这个解法仔细思考其实是更好的。
i从81开始递减,i/9每9个数更新一次,从8到0。
i%9每个数更新一次,对应正好就是完整的遍历。

正当我满以为已经找到最佳解法的时候,书上又提出了第三种解法!

#include <stdio.h>
int main(int argc, char *argv[])
{
    struct
    {
        unsigned a:4;
        unsigned b:4;
    }i;
    for(i.a=1;i.a<=9;i.a++)
        for(i.b=1;i.b<=9;i.b++){
            if(i.a%3!=i.b%3)
                printf("A:%d  B:%d\n",i.a,i.b);
        }
    return 0;
}

看到
struct
    {
        unsigned a:4;
        unsigned b:4;
    }i;
我一下子懵逼了。搜索了一下,发现这玩意是位域。

百科解释是:

位域 

位域是指信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有01 两种状态, 用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为"位域""位段"。所谓"位域"是把一个字节中的二进位划分为几 个不同的区域, 并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。

与结构定义相仿,其形式为:
struct 位域结构名
{ 位域列表 };
其中位域列表的形式为: 类型说明符 位域名:位域长度
例如:
struct bs
{int a:8;int b:2;int c:6;};
位域变量的说明
与结构变量说明的方式相同。 可采用先定义后说明,同时定义说明或者直接说明这三种方式。例如:
struct bs
{int a:8;int b:2;int c:6;}data;
说明data为bs变量,共占2个字节。其中位域a占8位,位域b占2位,位域c占6位。


恍然大悟啊!在第一种解法中我苦苦思索的对一个字节前后各四位的分别操作就是位域操作。

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页