象棋博弈-编程之美初学者参考文档 1.2

此文档的目的是帮助更多初学《编程之美》的Programmers少走弯路,致力于顺藤摸瓜。笔者也是一名编程初学者。实际编写中时有重新发明轮子的行为,并已患上重度查询文档症。精巧的算法往往伴随生僻的知识点,究其根本,结合编程之美具体实现,在此汇总发布。因水平所限,如有缺漏以及不严谨之处,请各位多多指教。 

博弈论 [1]  又被称为对策论(Game Theory)既是现代数学的一个新分支,也是运筹学的一个重要学科。 

在这里,我们探讨中国象棋的将帅问题。如图,为了下面叙述方便,我们约定用A表示将,B表示帅。

象棋博弈-编程之美初学者参考文档 1.2_第1张图片

 A、B两子被限制在乙方3*3的格子里运动。每一步,A,B分别可以横向或纵向移动一格,但不能沿对角线移动。另外,A不能面对B,也就是说,A和B不能处于同一纵向直线上。请写出一个程序,输出A,B所有合法的位置,要求在代码中只能使用一个变量。

 

在这里,我们采用byte数据类型,用前面的4bit表示A的位置,后面的4bit表示B的位置。那么存在16种情况满足题意。

总体思路很简单,可以用两个嵌套的for循环实现:

遍历A的位置
    遍历B的位置
        判断
        若,则

一.为什么使用byte类型?

我们用排除法,首先肯定不能用BOOL类型。因为它只有两个值。

事实上,对本题来说,每个子都只需要9个数字就可以表达Ta和TA的位置。(皮一下开心)

一个8位的byte类型能够表达2^8=256个值,足够了。

那问题在于,如何用bit级的运算将数据从这一byte变量的左边和右边分别存入和读出。

 

dim b为byte类型的通用实例。

我们对于任意一个b,可以把它赋为任何的两点坐标。进而,我们可以任意改变它的值。这个过程我们用LeftMask和RightMask实现,类似于计算机组成原理课上的并列与赋值方法。下面是做法:

对于byte b等于任何一个值,我们不妨假设为b=10100101。

声明LMask=11110000,RMask=00001111.
(L/RMask&b)&n即可

同样的,我们也可以获取b的任意坐标。

L/RMask & b即可

同样的,我们可以使用“<<” ,“>>”进行左右移。

 

二.如何在不声名其他变量约束的前提下创建一个for循环?

我们采用宏来抽象化代码。

for(LSET(b,1);LGET(b)<=GRIDW*GRIDW;LSET(b,(LGET(b)+1)))

反复利用1byte的循环单元,把它作为循环计数器并用前面提到的存取和读入方法进行操作。

这个宏的意思是:取b的左地址为1(0001),对于不大于9的b的左地址,/* Do:我们把每个地址完整遍历一次b的右地址。 */,之后b地址加一。

别忘了,b的左右地址分别代表将帅二子坐标:

1      2      3                1      2      3

4      5      6                4      5      6

7      8      9                7      8      9

那么,for循环也可以很容易的实现了。

三.解法

这里给出代码和我学习的一些理解:

请读者思考一个问题:

对于Line:18,为什么要对GRIDW进行一次“%”运算?直接比较不行吗?

代码练习一:

//为了更容易理解,尽可能将常量赋予定义,将功能打包为宏。
#include 
#define HALF_BITS_LENGTH 4
#define FULLMASK 255
#define LMASK (FULLMASK << HALF_BITS_LENGTH)
#define RMASK (FULLMASK >> HALF_BITS_LENGTH)
#define RSET(b,n) (b=((LMASK&b) ^ n))// ^ 是按位异或
#define LSET(b,n) (b=((RMASK&b) ^ (n<>HALF_BITS_LENGTH)
#define GRIDW 3//棋盘的行范围为3

int main()
{
	unsigned char b;
	for (LSET(b, 1); LGET(b) <= GRIDW * GRIDW; LSET(b, (LGET(b) + 1)))//先对A遍历
		for (RSET(b, 1); RGET(b) <= GRIDW * GRIDW; RSET(b, (RGET(b) + 1)))//再对B遍历
			if (LGET(b) % GRIDW != RGET(b) % GRIDW)//如果不冲突
				printf("A = %d , B = %d \n", LGET(b), RGET(b));//单项输出
	return 0;
}

很简单,因为我们要对比的是对于 列不变的{1,2,3} 的公倍数,例如数字5,位于第二列。我们通过5%3=2得到列数.

或许你会得到如下的错误答案:

//错误的答案,没有进行有效的列比较
A = 1 , B = 3
A = 1 , B = 4
A = 1 , B = 5
A = 1 , B = 6
A = 1 , B = 7
A = 1 , B = 8
A = 1 , B = 9
A = 2 , B = 1
A = 2 , B = 3
A = 2 , B = 4
A = 2 , B = 5
A = 2 , B = 6
A = 2 , B = 7
A = 2 , B = 8
A = 2 , B = 9
A = 3 , B = 1
A = 3 , B = 2
A = 3 , B = 4
A = 3 , B = 5
A = 3 , B = 6
A = 3 , B = 7
A = 3 , B = 8
A = 3 , B = 9
A = 4 , B = 1
A = 4 , B = 2
A = 4 , B = 3
A = 4 , B = 5
A = 4 , B = 6
A = 4 , B = 7
A = 4 , B = 8
A = 4 , B = 9
A = 5 , B = 1
A = 5 , B = 2
A = 5 , B = 3
A = 5 , B = 4
A = 5 , B = 6
A = 5 , B = 7
A = 5 , B = 8
A = 5 , B = 9
A = 6 , B = 1
A = 6 , B = 2
A = 6 , B = 3
A = 6 , B = 4
A = 6 , B = 5
A = 6 , B = 7
A = 6 , B = 8
A = 6 , B = 9
A = 7 , B = 1
A = 7 , B = 2
A = 7 , B = 3
A = 7 , B = 4
A = 7 , B = 5
A = 7 , B = 6
A = 7 , B = 8
A = 7 , B = 9
A = 8 , B = 1
A = 8 , B = 2
A = 8 , B = 3
A = 8 , B = 4
A = 8 , B = 5
A = 8 , B = 6
A = 8 , B = 7
A = 8 , B = 9
A = 9 , B = 1
A = 9 , B = 2
A = 9 , B = 3
A = 9 , B = 4
A = 9 , B = 5
A = 9 , B = 6
A = 9 , B = 7
A = 9 , B = 8

但是MSRA里却有人说,下面的一小段代码也能达到同样的目的:

代码练习2:

#include "stdio.h"
struct {
	unsigned char a : 4;//What’s ":"?
	unsigned char b : 4;
}i;
void main(){
	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);
}

首先我们先搞懂什么是位域

位域是指信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。这就是位域。

把一个字节中的二进位划分为几 个不同的区域, 并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。

这里,域名为a,b .

写一个小例子帮助大家快速理解:

struct Bit_Field
{
    int a:2;
    int b:6;
}BF;
//这里BF为Bit_Field变量,其中位域a占2位,位域b占6位,共占一个字节。

一个位域必须存储在同一个字节中,不能跨两个字节。如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。因此位域的长度不能大于一个字节的长度,也就是说不能超过8位二进位。如果用作填充和调整位置,位域可以无位域名,无名的位域是不能使用的。

那么,那种方式是效率最高的呢?我们利用VS性能探测器进行对比:

象棋博弈-编程之美初学者参考文档 1.2_第2张图片

与代码练习一对比,嗯。。笔者平台少占用约9KB内存。别的平台还没测,如果将范围放大为半个棋盘,那差距应该很可观。

 

书P18页尾,作者注的一句话很有意思。

这一题目来自微软亚洲研究院工程师Matt Scott,他在学习中国象棋的时候想出了这个题目,后来一位应聘者给出了比他的“正解”简明很多的答案,他们现在成了同事。

 

你可能感兴趣的:(BOE)