中国象棋的将帅问题

来源:互联网 发布:mac版spss 编辑:程序博客网 时间:2024/04/27 10:59

题目:下过中国象棋的朋友都知道,双方的“将”和“帅”相隔遥远,并且它们不能照面。在象棋残局中,许多高手能利用这一规则走出精妙的杀招。假设棋盘上只有“将”和“帅”二子(如图所示)(为了下面叙述方便,我们约定用A表示“将”,B表示“帅”):

AB二子被限制在己方3×3的格子里运动。例如,在如上的表格里,A被正方形{d10,f10,d8,f8}包围,而B被正方形{d3,f3,d1,f1}包围。每一步,AB分别可以横向或纵向移动一格,但不能沿对角线移动。另外,A不能面对B,也就是说,AB不能处于同一纵向直线上(比如Ad10的位置,那么B就不能在d1d2以及d3)。

请写出一个程序,输出AB所有合法位置。要求在代码中只能使用一个变量。

我们介绍三种方法并进行对比。

第一种解法:

题目要求只用一个变量,我们却要存储A和B两个棋子的位置信息,该怎么办呢?

先把已知变量类型列举一下,然后分析。

对于bool类型,估计没有办法做任何扩展了,他只能表示true和false两个值;而byte或int类型,能够表达的信息则更多。事实上,对本题来说,每个子都只需要9个数字就可以表示它的全部位置。

一个8位的byte类型能够表达256个值,用它来表示A和B 的位置信息绰绰有余,把这个字节的变量(设为b)分成两部分。用前面的4bit表示A的位置,用后面的4bit表示B的位置,而4bit可以表示16个数,这完全足够。现在问题在于:如何使用bit级的运算将数据从这一byte变量的左边和右边分别存入和读出。

下面是做法:

1.将byte b(10100101)的右边4bit(0101)设为n(0011):

首先清除b 右边的bits,同时保持左边的bits:

   11110000(LMASK)

& 10100101(b)          

   10100000

然后将上一步得到的结果与n做或运算

    10100000(LMASK &b)

|   00000011 (n)                 

    10100011 

 

2..将byte b(10100101)的左边4bit(1010)设为n(0011):

首先清除b 左边的bits,同时保持右边的bits:

   00001111(RMASK)

& 10100101(b)          

   00000101

现在把n移动到byte数据的左边

n<<4=00110000

然后对以上两步得到的结果做或运算

    00000101(RMASK &b)

|   00110000(n<<4)                 

    00110101

3.得到byte数据的右边4bits或左边4bits(e.g.10100101中的1010以及0101):

清除b左边的bits,同时保持右边的bits:

    00001111(RMASK)

& 10100101(b)          

   00000101

清除b右边的bits,同时保持左边的bits:

    11110000(LMASK)

& 10100101(b)          

   10100000

将结果右移4bits

10100000>>4=00001010

最后的挑战是如何在不声明其他变量的约束的前提下创建一个for循环。可以重复利用1byte的存储单元,把它作为循环计数器并用前面提到的存取和读入技术进行操作,还可以用宏来抽象化代码。

解法一代码如下:

ChessGeneral.h

<span style="color:#000000;">#include <stdio.h>#include <iostream>using namespace std;#define  HALF_BITS_LENGTH 4                                            //这个值记忆存储单元长度的一半,在这里是4bit#define FULLMASK  255                                                       //表示一个全部bits的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#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                                                                 //将帅、将移动范围的行宽度void simulation1();</span>
<span style="color:#000000;">main.cpp</span>
<span style="color:#000000;">#include "ChessGeneral.h"void main(){simulation1();}void simulation1(){int i=0;unsigned char b;cout<<"simulation1:"<<endl;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){cout<<"A="<<LGET(b)<<","<<"B="<<RGET(b)<<"   ";i++;if (i%3==0){cout<<endl;}}}}}</span>
<span style="color:#000000;">运行结果如下:</span>
<span style="color:#000000;"><img src="http://img.blog.csdn.net/20141216114513875?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2luYXRfMjQ1MjA5MjU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" /></span>

第二种解法:

只能使用一个变量nNum ==> 只能使用一个循环,nNum只能用来表示A、B位置的组合,nNum最大为9×9-1=80;

 如何用nNum表示一个A、B位置的组合?

下图表示A(红色)、B(蓝色)所在位置:

 

 

678345012678345012

以nNum%9表示A的位置,nNum/9表示B的位置,如nNum==15,A==6,B==1。

又因为:

81/9=9,80/9=9,79/9=9,78/9=9,77/9=9,76/9=9,75/9=9,74/9=9,73/9=9,72/9=8,...,

9%9=0,8%9=8,7%9=7,6%9=6,5%9=5,4%9=4,3%9=3,2%9=2,1%9=1,0%9=0,

如何确定A、B位置的合法性?

规则都指定了,合法性的确定也就很简单了:A%3 != B%3。

解法二代码:

<span style="color:#000000;">#define BYTE unsigned char void main(){simulation2();}void simulation2(){BYTE i=81;int j=0;cout<<"simulation2:"<<endl;while(i--){if (i/9%3==i%9%3){continue;}cout<<"A="<<i/9+1<<","<<"B="<<i%9+1<<"   ";j++;if (j%3==0){cout<<endl;}}}</span>

运行结果:


第三种解法:

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

使用占用一个字节的结构体变量

struct
 {
  unsigned char a:4;
  unsigned char b:4;
 }General;
unsigned char a:4;表示结构体变量a只使用其中的低4位;高4位可用作他用,在这里是给变量b使用了。

解法三代码如下:

<span style="color:#000000;">void main(){simulation3();}void simulation3(){int i=0;cout<<"simulation3:"<<endl;struct {unsigned char a:4;unsigned char b:4;//表示结构体变量a只使用其中的低4位,高4位可用作他用,在这里是给变量b使用了。}General;for(General.a=1;General.a<=9;General.a++){for (General.b=1;General.b<=9;General.b++){if (General.a%3!=General.b%3){cout<<"A="<<(int)General.a<<","<<"B="<<(int)General.b<<"   ";i++;if (i%3==0){cout<<endl;}}}}}</span>

运行结果为:


三种方法的对比:

不同点:

解法一使用#define定义的标识符不是变量,它只用作宏替换,因此不占有内存,解法一代码宏定义略复杂;

解法二代码简洁,逻辑关系不清楚,不易扩展;

解法三逻辑关系清楚明了。

相同点:

结果都正确,循环81次,计算量相当。

根据多次重复,统计可得三个算法运算时间相当,解法二较其他两个较快。

综上,解法二无论是空间上还是时间上性能都很好,解法一次之。

 

 

 

 

 

 

 

0 0