下过中国象棋的朋友都知道,双方的“将”和“帅”相隔遥远,并且不能照面。在象棋残局中,许多高手能利用这一规则走出许多精妙的杀招。假设棋盘上只有“将”和“帅”二子。(下面为了叙述方面,我们约定用A表示“将”,B表示“帅”)。
A和B分别被限制在自己的九宫格内,不能走出九宫格,不能走斜线,只能走横竖线上的一步。
请写出一个程序,输出A、B所有合法位置。要求代码中只能使用一个字节存储变量。
问题本身并不复杂,只要把所有A、B互相排斥的条件列举出来就可以完成本题的要求。由于本题要求只能存储使用一个变量,所有首先必须想清楚在写代码的时候,有哪些信息需要存储,并且尽量高效地存储信息。稍微思考一下,可以知道这个程序的大体框架是:
遍历A的位置
遍历B的位置
判断A,B的位置组合是否满足要求。
如果满足,则输出。
因此,需要存储的是A、B的位置信息,并且每次循环都要更新,首先创建一个逻辑坐标系统,一个可行的办法是用1-9的数字,按照行有限的顺序来表示每个格点的位置。这样,只需要用模余运算就可以得到当前的列号,从而判断A、B是否相斥
1 2 3
4 5 6
7 8 9
第二,题目要求只用一个变量,我们却要存储A和B两个子的位置信息,该怎么办呢?
对于bool类型,没有办法做任何扩展了,因为它只能表示true和false两个值;而byte或int类型,它们能够表达的信息则更多。事实上,对于本题来说,每个子都只需要9个数字就可以表示它的全部位置。
一个8位的byte类型能够表达2^8=256个值,所以用它来表示A、B的位置信息绰绰有余。因此可以把这个字节的变量(设为b)分为两部分。用前面的4bit表示A的位置,用后面的4bit表示B的位置,而4个bit可以表示16个数。这已经足够了。
问题在于:如何使用bit级的运算将数据从这一byte变量的左边和右边分别存入和读出。
代码如下:
#include
#define HALF_BITE_LENGTH 4
#define FULIMASK 255
#define LMASK (FULIMASK << HALF_BITE_LENGTH)
#define RMASK (FULIMASK >> HALF_BITE_LENGTH)
#define RSET(b,n) (b=((LMASK&b)|(n)))
#define LSET(b,n) (b=((RMASK&b)|((n) << HALF_BITE_LENGTH)))
#define RGET(b) (RMASK & b)
#define LGET(b) ((LMASK & b) >> HALF_BITE_LENGTH)
#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 \n", LGET(b), RGET(b));
return 0;
}
A= 1, B= 2
A= 1, B= 3
A= 1, B= 5
A= 1, B= 6
A= 1, B= 8
A= 1, B= 9
A= 2, B= 1
A= 2, B= 3
A= 2, B= 4
A= 2, B= 6
A= 2, B= 7
A= 2, B= 9
A= 3, B= 1
A= 3, B= 2
A= 3, B= 4
A= 3, B= 5
A= 3, B= 7
A= 3, B= 8
A= 4, B= 2
A= 4, B= 3
A= 4, B= 5
A= 4, B= 6
A= 4, B= 8
A= 4, B= 9
A= 5, B= 1
A= 5, B= 3
A= 5, B= 4
A= 5, B= 6
A= 5, B= 7
A= 5, B= 9
A= 6, B= 1
A= 6, B= 2
A= 6, B= 4
A= 6, B= 5
A= 6, B= 7
A= 6, B= 8
A= 7, B= 2
A= 7, B= 3
A= 7, B= 5
A= 7, B= 6
A= 7, B= 8
A= 7, B= 9
A= 8, B= 1
A= 8, B= 3
A= 8, B= 4
A= 8, B= 6
A= 8, B= 7
A= 8, B= 9
A= 9, B= 1
A= 9, B= 2
A= 9, B= 4
A= 9, B= 5
A= 9, B= 7
A= 9, B= 8
E:\source\repos\C_Demo\Debug\C_Demo.exe (进程 10136)已退出,返回代码为: 0。
若要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口...
另一种解法:
因为两个棋子都只有9种摆放的位置,两个组合最多有9*9=81种位置,那么我们拥有的变量只要可以表示这么多变量就可以了,这样的话byte,char都是可以的选择。
int main() {
int 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;
}
这段代码的想法就是把两个数据的存储方式变为一个数字,这个数字是(A+1)*9+(B+1),两个加一是为了让最小的数字比如0–9之间的数也能起到作用。因为是从零开始,所以直到80就完成了对所有可能性的扫描。
其中的结果要%3是因为需要知道AB位置的行数,如果行数相等就不输出,如果不相等就输出AB的值。
第三种解法:
struct
{
unsigned char a : 4;
unsigned char b : 4;
} i;
int main() {
for (i.a = 1; i.a <= 9; i.a++)
{
for (i.b = 0; i.b <= 9; i.b++)
{
if (i.a % 3 != i.b % 3) {
printf("A=%d ,B=%d \n", i.a, i.b);
}
}
}
return 0;
}
这段代码的想法和上一个差不多,只是存储方式更加直接,扫描方式也更加直接,没有异议。用数组中的第一个数表示A的位置,用数组中的第二个数表示B的位置。只是两个byte是两个字节,是超过字节限制。(如果不考虑字节限制的话这也是一个不错的方法。)