编程之美1.2——将帅问题

1.问题描述:

学过象棋的朋友一定不会陌生,中国象棋中规定了,将帅不能碰面,也就是说,将帅是不可能出现在相同的一列上的 通过二重循环的遍历操作我们可以通过枚举的方法简单的求出将帅所有合适的位置组合,但是,现在的问题是,如何只利用一个字节的存储空间,将将帅所有的合适的位置信息都描述出来

2.解析:(解法1)

2.1

本问题中,我们的核心问题在于如何利用一个字节来表示不同的位置信息 首先我们先要想到,对于将帅的位置信息,我们可以采用如下的排列方式来进行存储的优化 1
2
3 4
5
6 7
8
9 将帅的位置都可以这么描述,这样的话,我们只需要一种存储空间就可以表示我们的列信息(我们只要对3取模就可以了)

那么现在问题来了,我们如何只用一个字节来完成这个工作呢

2.2

我们需要了解到我们的位置空间只有9种情况,9的二进制只有4位,刚好,一个字节的二进制有8位,那么之歌问题就变得迎刃而解了,我们可以将一个字节的分成左右两半分别存储将和帅的位置信息,取模之后如果出现了相同,说明在一列,这种情况continue就好,然后输出所有的情况就好了

2.3

该算法的核心在于位运算和定义宏: 首先我们讲解一下本问题中我们需要涉及到的位运算有哪些:(位运算之后的内容我会再开一篇博客全面讲解) 1.
&:按位与操作: 按位与操作的内容是两个数字的二进制对应位比较,同为1则为1,否则为0 2.
|:按位或操作:(操作完返回的内容必须要接收,因为是二元运算符) 按位或操作的内容是两个数字的二进制对应位比较,存在1则为1,否则为0 3.
>>/<<:右移和左移操作:(操作完返回的是本身,是一元运算符) 按位右移和左移操作的内容是我们将数字对应的二进制整体向右或者向左移动,空位用0补齐

下面我们讲解必要的定义宏的操作: 我们定义宏的时候,相当于我们将定义的部分在代码中全部强制替换掉,所以说可能会带来由县级的问题,这里我们加上括号可以很好的解决

2.4代码实现:(对必要的步骤有详细的讲解)

#include"iostream"
#include"cstdio"
#include"cstring"
#include"cstdlib"

//开始定义运算宏    n<=9 n只会用到四个存储单元,就是半个字节 
#define halfbitlength 4   //一个bit单元长度,我们一次用一半 
#define fullmask 255   //11111111八个1
#define lmask (fullmask<<halfbitlength)   //左移半个字节,左边当做一个存储单元,结果为11110000
#define rmask (fullmask>>halfbitlength)   //右移半个字节,右边当做一个存储单元,结果为00001111
#define rset(b,n) (b=((b & lmask) | (n)))   //将b的右边半个字节设置成n   (b&lmask的作用是将右边半个字节先全部置空,再用或运算复制) 
#define lset(b,n) (b=((b & rmask) | (n<<halfbitlength)))   //思路同上
#define lget(b) (b>> halfbitlength)
#define rget(b) (rmask & b)

using namespace std;

unsigned char b;

int main()
{
	for(lset(b,1);lget(b)<=9;lset(b,(lget(b)+1)))
	{
		for(rset(b,1);rget(b)<=9;rset(b,(rget(b)+1)))
		{
			if(lget(b)%3==rget(b)%3) continue;
			else printf("A=%d,B=%d\n",lget(b),rget(b));
		}
	}
	return 0;
}

3.解法:(本人觉得最神奇的代码,优化二重循环)

在这里我们先援引一位大神的讲解:
博客专家对解法2的讲解 在这里,我们优化的核心就是一段代码:  
var=(var/9)*9+var%9
没错,上面的就是我们的优化的核心
我们先附上代码:

#include"iostream"
#include"cstdio"

using namespace std;

unsigned char i=81;

int main()
{
	while(i--)
	{
                if(i/9%3==i%9%3) continue;
		else printf("A=%d,B=%d\n",i/9+1,i%9+1);
	}
	return 0;
}

在这里我们可能会看不懂这么一句话:if(i/9%3==i%9%3)  continue;
在这里就隐含着我们对多重循环的处理的一个超级神奇的优化操作
i=81的由来是9*9有81种将帅站位方式(我们以后再做相同的处理优化的时候,这相当于是所有的情况的总数目)
然后,i/9相当于是外层循环,i%9相当于是内层循环
因为我们已经知道了上面的一个公式:var=(var/9)*9+var%9;当然这里的9可以随意的替换,我们懂原理就好
这里我们巧妙的利用了i/9在没有9的进位下的时候值是不变的,但是i%9是相对会改变的,这样子我们就将一个二重循环巧妙的抓化成一个一重循环,虽然复杂度没有优化,但是我们还是可以看出一些精妙之处的


对于解法三我们进行一下拓展:利用钢材的原理性质,来做几道枚举的小题目

#include"iostream"
#include"cstdio"
#include"cstring"
#include"cstdlib"
#include"cmath"

//求x^2+y^2=2000的正整数解
//45*45>2000,所以说按照之前的
 
using namespace std;

int main()
{
	int i=45*45;    //情况总数
	while(i--)
	{
		if(pow(i/45,2)+pow(i%45,2)==2000) 
		{
			printf("%d^2+%d^2==2000\n",i/45,i%45); 
			break;
		}
	} 
	return 0;
}

对于2*3*4的三重循环来说,我们可以这么来分类看待循环过程:
i/2 ||| i%2(最后一层只有2种情况的循环)
i/2/3 i/2%3(第二层循环)
i/2/3/4 i/2/3%4(第三重循环)

4.总结:

编程世界如此广阔,此刻不再停留

    原文作者:GMFTBY
    原文地址: https://blog.csdn.net/ltyqljhwcm/article/details/52203578
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞