近期开始学习
TAOCP第4卷第2册,书中一开始就介绍了Gray二进制码,觉得非常有趣,就参照书中的算法写一个小程序来生成Gray码。
开始之前,我们先来看看什么是
Gray二进制码。Gray二进制码是以一种简单和规则的方式来排列所有2^n个n位的二进制串,即每次仅改变一个二进制位。例如,对于n=4,Gray二进制码是:
0000, 0001, 0011, 0010, 0110, 0111, 0101, 0100,
1100, 1101, 1111, 1110, 1010, 1011, 1001, 1000
书上说,在进行模拟信息与数字信息之间的相互转换时,
Gray二进制码非常有用,但具体如何使用我也不太清楚。我们只是来看看,对于任意给定的n,如何生成相应的Gray二进制码。
书中给出了一个最简单的生成
Gray二进制码的规则。如果Γn表示n位二进制串的Gray二进制码,则我们可以通过以下两个规则来递归地定义Γn:
Γ
0 = ε
Γ
n +1 = 0Γn,1Γn
R
这里ε表示空串,
0Γn表示把0加到Γn序列中的每个串前面所得到的序列,Γn
R表示把Γ
n序列反序,1Γn
R则表示把
1加到Γn
R序列中的每个串前面所得到的序列。由于Γ
n的最后一个串等于Γn
R的头一个串,所以显然可知,如果Γ
n满足Gray码的性质,则Γn+1也恰好满足。
由于每次只改变一个二进制位,所以如果我们从全
0串开始计算Gray码,则肯定得到的第1,3,5,7,……个串有偶数个位为1,而第2,4,6,8,……个串则有奇数个位为1。经过一番推论,书中给出了一个生成Gray二进制码的算法:
从全
0串开始,第一个串为全0串;如果当前串有偶数个位为1,则下一个串为将当前串最低位反转所得;如果当前串有奇数个位为1,则找出当前串中为1的最低位,将该位的高一位反转即可得到下一个串;循环以上步骤直至n位二进制串溢出。
相应的程序代码如下:
#include <iostream>
#include <cstdlib>
using namespace std;
void Gray(int n)
{
unsigned long g = 0;
unsigned long max = (unsigned long)(1L) << n;
do
{
cout << g << " ";
g ^= 1;
cout << g << " ";
g ^= (g ^ (g-1)) + 1;
}
while (g < max);
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
cerr << "Usage: " << argv[0] << " <n>/n";
return 1;
}
int n = atoi(argv[1]);
if (n < 1 || n > 30)
{
cerr << n << " is too big or too small/n";
return 2;
}
Gray(n);
return 0;
}
|
由于用的是
32位机器,所以我把n限制为30以内。main()函数无须多说,主要的算法在于Gray()函数。变量g为每次输出的Gray码,从0开始循环,变量max用于检测g是否溢出;每次循环产生两个Gray码,第一个有偶数位1,第二个有奇数位1。从偶数位1的串到奇数位1的串很简单,g ^= 1; 即是将最低位反转。从奇数位1的串以偶数位1的串的生成则有点技巧了。
g ^= (g ^ (g-1)) + 1;
我来解释一下。首先,假设
g中为1的最低位是第i位,即g的第i位为1,而所有比第i位低的位全为0;那么g-1可以将g的第i位变为0,而所有比第i位低的位全部由0变为1;这样g与g-1相比,第i位以上的所有位(不含第i位)全部相同,第i位以下的所有位(含第i位)全部相异,所以g ^ (g-1)得到的结果是:第i位以上的所有位(不含第i位)全部为0,第i位以下的所有位(含第i位)全部为1;这个数加1,就得到了除了第i+1位为1其它所有位为0的串;让g与这个串异或,即是将g的第i+1位反转。
是不是说得有点复杂呢?我们用一个例子来说明一下,就会容易明白了。假设现在的
g为1110,则g-1为1101,两者异或得到0011,加1得到0100,将此数与g异或最终得到1010。恰恰是把g中为1的最低位的高一位反转。
我们对此程序输入
n=4,运行得到以下结果:
0 1 3 2 6 7 5 4 12 13 15 14 10 11 9 8
由于不是二进制表示,所以看起来有点乱。如果你将它们转为二进制,就可以看到输出的结果是正确的,与本文前面给出的
Gray二进制码例子是相符的。