分享这个链接:http://book.douban.com/annotation/13753057/
引子问题:中国象棋将帅问题:分析与解法:这个问题的解法并不复杂。 遍历A的所有位置 遍历B的所有位置 如果A的位置和B的位置不在同一列 输出结果 否则 继续寻找 地图可以用0-8表示A或B可能的9个位置 0------1------2 3------4------5 6------7------8 关键问题在于只使用一个变量来表示A和B的位置。所以可以使用位运算来解决。一个无符号字符类型的长度是1字节,也就是8位,8位可以表示2^8=256个值,对于A、B的9个 位置来说足够。可以用前4位来表示A的位置情况,后4位表示B的位置情况。而4位可以表示16个数也足够表示A、B的位置情况了。通过位运算可以对A、B的位置进行读取和修改。 几种基本的位运算: (1)& 按位与运算 (2)| 按位或运算 "与"和"或"就不用说了吧 (3)^ 按位异或运算 相同为假,不同为真 (4)~ 按位取反 一元运算符 (5)<< 按位左移 如 0000 0111 << 2 = 0001 1100,将此数左移两位相当于将此数扩大两倍。 (6)>> 按位右移 如 0001 1000 >> 2 = 0000 0110,将此数右移两位相当于将此数缩小两倍。 令LMASK为1111 0000,另任意一个1字节的字符型变量与其做与运算,结果右移四位,便可得到此变量的高四位的值。 Example, 0110 1011&1111 0000= 0110 0000 >> 4 = 0000 0110同理,令RMASK为0000 1111,即可得到它低四位的值。 Ex. 0110 1011& 0000 1111= 0000 1011设置1字节字符型变量,比如对高四位进行设置,先将变量与RMASK相与, 将要修改的变量左移四位后于前一结果进行“异或”或“或运算”。 Ex.将0110 1011高四位设置为1001. 0110 1011& 0000 1111= 0000 1011 0000 1001 << 4 = 1001 0000^ 1001 0000= 1001 1011同样的方法设置低四位的值。 难以解决的问题:如何移动到相邻元素,且不过界? set的范围是1~9,每次加一,考虑了所有9*9种组合。这样就释然了。 但,还有一点,就是如何在不声明其他变量约束的前提下,创建一个for循环:重复利用1byte的存储单元,把它作为循 环计数器并用前面提到的存取和读入技术进行操作,代码:在一把象棋的残局中,象棋双方的将帅不可以相见,即不可以在中间没有其他棋子的情况下在同一列出现。而将、帅各被限制在己方的3*3的格子中运动。相信 大家都非常熟悉象棋的玩法吧,这里就不详细说明游戏规则了。 用A、B代表将和帅,请写出一个程序,输出A、B所有合法的位置。要求在代码中只能用一个变量。#define FULLMASK 255 //这个数字表示一个全部bit的mask,在二进制表示中,它是11111111 #define LMASK (FULLMASK << HALF_BITS_LENGTH) //这个宏表示左bits的mask,在二进制表示中,它是11110000 #define RMASK (FULLMASK >> HALF_BITS_LENGTH) //这个宏表示右bits的mask,在二进制表示中,它是00001111 #define RSET(b, n) (b = ((LMASK & b) ^ n)) //这个宏,将b的右边设置成n,<pre id="link-report"><span style="font-size:18px;"></span><pre name="code" class="cpp">//(LMASK & b)是将b的低位部分清零,然后与n按位异或,即为n #define LSET(b, n) (b = ((RMASK & b) ^ (n << HALF_BITS_LENGTH))) //这个宏,将b的左边设置成n #define RGET(b) (RMASK & b) //这个宏得到b的右边的值 #define LGET(b) ((LMASK & b) >> HALF_BITS_LENGTH) //这个宏得到b左边的值 #define GRIDW 3 //这个数字表示将帅移动范围的行宽度 int main() { unsigned char b; for(LSET(b, 1); LGET(b) <= GRIDW * GRIDW; LSET(b, (LGET(b) + 1))) { for(RSET(b, 1); RGET(b) <= GRIDW * GRIDW; RSET(b, (RGET(b) + 1))) { if(LGET(b) % GRIDW != RGET(b) % GRIDW) { printf("A = %d, B = %d\t", LGET(b), RGET(b)); } } } return 0;
算法本身很简单,重点是位运算的应用。<BOP>上还有两个更简洁的算法:第一个:
- #include<stdio.h>
- #define BYTE unsigned char
- int main(void)
- {
- BYTE i = 81;
- while(i--)
- {
- if((i / 9) % 3 == (i % 9) % 3)
- continue;
- printf("A = %d, B = %d\n", i /9 + 1, i%9 + 1);
- }
- return 0;
- }
则i/9的计算机处理结果,即结果直接去掉小数点后部分的结果即是此九进制数的第二位,
而i%9即是此九进制数的个位。本程序用此九进制数的第二位保存A的位置,个位表示B的位置。
最大值为88,即为十进制的80.程序从十进制的80,即九进制的88遍历到十进制的0,即九进制的0.
将符合条件的位置全部输出。第二个:
- #include<stdio.h>
- int main(void)
- {
- struct
- {
- unsigned char a:4;
- unsigned char 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;
- }
用,建议尽量少用,会破坏程序的移植性。当结构体中的元素的取值范围很小时,可以将几个字段按位合成一个字段
来表示,起到节省内存空间的作用。
Ex:
- #include<stdio.h>
- int main(void)
- {
- struct test
- {
- unsigned char a:4;
- unsigned char b:4;
- }i;
- i.a = 15;
- i.b = 10;
- printf("%d\n", sizeof(i));
- }
结构。看下面的例子:
- #include<stdio.h>
- int main(void)
- {
- struct test
- {
- int a;
- char b;
- }i;
- printf("%d\n",sizeof(i));
- }
- #include<stdio.h>
- int main(void)
- {
- struct test
- {
- int a;
- char b,c;
- }i;
- printf("%d\n",sizeof(i));
- }
因此各种变量的size都一般都是4的倍数。而且结构体数据都是按照定义时所使用的顺序存放的,因此在第一个例子
中尽管b变量只会占有一个字节,但是a + b = 5 > 4,因此第一个4个字节存放a,第二个4个字节用于存放b,这样实
际上就浪费了3个字节。在第二个例子中第二个4个字节用来存放b和c。所以,在结构体中要注意结构体中的变量定义
的顺序,不同的顺序可能会造成占用空间的不同。这在嵌入式程序设计等系统资源比较少的情况下尤为重要。比如如
下两种结构体:
- #include<stdio.h>
- struct m
- {
- char a;
- int b;
- char c;
- }x;
- struct n
- {
- char a;
- char c;
- int b;
- }y;
- int main(void)
- {
- printf("m:%d\nn:%d\n", sizeof(x), sizeof(y));
- return 0;
- }
当的顺序会造成空间的浪费。读者可以想到发生这样情况的原因的。所以建议声明结构体时,按照不同变量的类型,
按占用空间的大小升序或降序声明会取得较好的空间占用。