【编程之美】妙用位域解中国象棋将帅问题
问题描述
假设在中国象棋中只剩下将帅两个棋子,国人都知道基本规则:将帅不能出九宫格,只能上下左右移动,不能斜向移动,同时将帅不能照面。问在这样条件下,所有可能将帅位置。要求在代码中只能使用一个字节存储变量。
题目解答
如果放弃只要一个字节存储变量的条件,这道题简直就是helloworld级别的题目,问题在于我们如何让两个for循环中的两个变量放在一个字节中,在开始的分析中,作者使用大量的位运算来使得两个变量存储在同一个字节中,程序变得就比较“臃肿”了。具体代码见下:
1 #include<stdio.h> 2 #define HALF_BITS_LENGTH 4 3 #define FULLMASK 255 4 #define LMASK (FULLMASK << HALF_BITS_LENGTH) 5 #define RMASK (FULLMASK >> HALF_BITS_LENGTH) 6 #define RSET(b,n) (b = (b & LMASK) | (n)) 7 #define LSET(b,n) (b = ((b & RMASK) | ((n) << HALF_BITS_LENGTH))) 8 #define RGET(b) (b & RMASK) 9 #define LGET(b) ((b & LMASK)>>HALF_BITS_LENGTH) 10 #define GRIDW 3 11 12 int main() 13 { 14 unsigned char b; 15 for(LSET(b,1);LGET(b) <= GRIDW * GRIDW;LSET(b,(LGET(b)+1))) 16 { 17 for(RSET(b,1);RGET(b) <= GRIDW * GRIDW;RSET(b,(RGET(b))+1)) 18 { 19 if(LGET(b) % GRIDW != RGET(b) % GRIDW) 20 { 21 printf("A=%d,B=%d\n",LGET(b),RGET(b)); 22 } 23 } 24 } 25 return 0; 26 }
然而上面的方法是在是太繁琐了,在题目的最后,给出了使用“位”域实现该题目的简洁方法。
位域在百度百科中的解释是:“位域是指信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几个不同的区域, 并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。”
位域的定义方式类似于结构体:
struct 位域结构名 { 位域列表 };
其中位域列表的形式为: 类型说明符 位域名:位域长度。
例如:struct bs{int a:8;int b:2;int c:6;};
有了位域这个好东西,我们可以直接指定一个位的前四位和后四位为一个位结构体的两个子成员,相当于我们有了定义两个变量的能力。问题迎刃而解,具体代码如下:
1 #include<stdio.h> 2 struct { 3 unsigned char a:4; 4 unsigned char b:4; 5 } i; 6 int main() 7 { 8 for(i.a = 1; i.a <= 9;i.a++) 9 for(i.b = 1;i.b <=9;i.b++) 10 if(i.a % 3 != i.b % 3) 11 printf("A=%d,B=%d\n",i.a,i.b); 12 return 0; 13 }
参考文献:
1. 《编程之美》P13-P15
2. 百度百科:位域 http://baike.baidu.com/view/1256879.htm