【编程之美】游戏之乐——中国象棋将帅问题

中国象棋将帅问题


问题描述

下过中国象棋的朋友都知道,双方的“将”和“帅”相隔遥远,并且不能照面。在象棋残局中,许多高手能利用这一规则走出许多精妙的杀招。假设棋盘上只有“将”和“帅”二子。(下面为了叙述方面,我们约定用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是两个字节,是超过字节限制。(如果不考虑字节限制的话这也是一个不错的方法。)

你可能感兴趣的:(数据结构与算法)