最近,在洛谷里刷题时遇见了关于格雷码的题:
像往常一样,我去题解里看看大佬们的方法是什么样的,
偶然间,我看到第一位的大佬巧妙的方法:
短短8行代码,却让人摸不着头脑。
于是,在寻找和学习了许多资料后,写下这篇文章当作笔记。
首先,通过读题以及简单的观察规律,我们可以知道输出n 位格雷码中的 k 号二进制串要求我们输出的是从0开始第k个格雷码,而且该格雷码位数应该是n位(如输出4位的第四个格雷码便是 0110,3位便是110,5位的第14个格雷码是01001)
再回过神观看这位大佬的题解,运用的结论是第k个格雷码Gk = k xor (k>>1)(k从0数起),即
但是,这结论又是怎么来的呢?还是令人费解。
在学习了几篇文章下,我发现这给结论正是二进制转格雷码的方法,于是恍然大悟。
原理: 若二进制码表示为: B[N-1]B[N-2]...B[2]B[1]B[0];
相应地, 则二进制格雷码表示为: G[N-1]G[N-2]...G[2]G[1]G[0].
其中最高位保留: G[N-1] = B[N-1];
其他各位: G[i] = B[i+1] xor B[i]. (i = 0, 1, 2, ..., n-2)
但是,至于这个原理如何证明,我找到了这篇文章:
以下为他的证明:
我们假设不知道这个结论,我们寻找一种结论来用k表示Gk。
首先考虑 ,这个式子必须只有一位为1。
我们进行分类讨论:
① 如果k的最低位为0,则k+1最低位为1,没有产生进位,此时要使式子只有一位为1,将k和k+1异或即可,。
由这种情况我们可以假设 ,这在第一种情况成立,然而在第二种情况不成立。
② 如果k的最低位为1,则k+1最低位为0,此时发生了进位。
举个例子,
则
我们如何改进 这个公式,使得这个公式既符合第一种情况又符合第二种情况呢?
只要把1111异或上0111就得到了1000。
我们容易发现
由异或运算的性质得到
观察容易知道我们若设 ,则符合第二种情况,同时也符合第一种情况。
这样就得到了这个符合全部情况的结论
以上就是怎样转换格雷码,但是最后一句代码好像也需要简单分析一下。
while(~--n) cout<<(k>>n&1);
首先,看这个循环条件 ~--n,如果n我们输入的为3,这个条件中的值会是-3,-2,-1然后终止while运行。这部分没什么问题,但是k>>n&1是如何逐位输出i位的格雷码呢?
我们可以看出,k>>n的这几次分别会将n位中每一位移到其最低位,并与1进行按位与运算.因此可以通过这种方式格雷码的前k位.
(如00101 >>3 & 00001= 0, 00101 >> 2 & 00001 =1, 00101>>1&00001 = 0, 00101 >> 0 & 1 =1 所以当k=6, n=4,k^= k>>1 后k=5时, 4位的第六个格雷码便是0101)
当然,在大佬给出这些算法前,我怎么想也想不出来.
看来我还需进行很大的努力.