1. 格雷码简介
格雷码跟8421码一样,也是一种对数字进行二进制编码的方法,只是编码方法跟常见的8421二进制编码方法不一样。
格雷码序列中,相邻的两个数字的二进制编码只有一位是不一样的,这种编码方式常用于较复杂的电路中。大家都知道二进制的 1/0
代表了电路的 开/关
,例如n位的二进制编码,有很多情况下电路需要遍历 0 ~ 2^n
的情况,在电路实现中就是用n位的电路的开关依次转换来实现。
由于8421二进制编码的 0 ~ 2^n
的序列,相邻的两个数的二进制相差会出现多位不一样,因此电路在遍历时就需要同时变更多位电路开关,而这种操作是需要时间的,在这个过程中就有可能出现中间的状态,从而出现错误的编码。
例如:
n = 3 的 8421 编码
i | 8421编码 |
---|---|
0 | 000 |
1 | 001 |
2 | 010 |
3 | 011 |
4 | 100 |
5 | 101 |
6 | 110 |
7 | 111 |
相邻两个数之间的二进制编码相差的不止1位,就会出现上面说的情况。而格雷码编码中,相邻的两个数的二进制编码只有1位是不一样的,因此在物理电路的变换中只涉及到一位电路的开关操作,即操作是原子的
,不会出现中间过程,也就不会出现错误编码了,因此在某些场景的电路信号中被广泛运用。
2. 格雷码与8421二进制码之间的转换
以 n = 3
为例,8421码与格雷码之间的对应关系如下:
格雷码也是以全0开头的
i | 8421编码 | 格雷码 |
---|---|---|
0 | 000 | 000 |
1 | 001 | 001 |
2 | 010 | 011 |
3 | 011 | 010 |
4 | 100 | 110 |
5 | 101 | 111 |
6 | 110 | 101 |
7 | 111 | 100 |
2.1 8421码 ==> 格雷码
算法
1、8421码最左边一位不变,保留下来成为格雷码的最左边一位;
2、从左边第二位开始,将8421码的每一位与它左边的一位相 异或
得到对应位的格雷码;
代码实现
public static int toGrayCode(int i) {
return i ^ (i >> 1);
}
将 i
与 i - 1
异或,也即将 i 的每一位与它的左边一位相异或。右移操作左边是补的0,0与任何数异或等于其本身,因此最左边一位被保留了。
2.2 格雷码 ==> 8421码
算法
1、格雷码的最左边一位保持不变,保留下来成为8421码的最左边一位;
2、从左边第二位开始,将格雷码的每一位与前一位计算得到的8421码 异或
得到当前的8421码;
解释
8421码转格雷码是通过 i ^ (i - 1) 实现的,要想将格雷码转换为8421码,我们知道,异或运算有如下性质:
(a ^ b) ^ b = a ^ (b ^ b) = a
因此,可以得到 (i ^ (i >> 1)) ^ (i >> 1) = i ^ ((i >> 1) ^ (i >> 1)) = i
所以在上面的算法中在计算当前位的8421码时,需要将当前位的格雷码与上一位计算得到的8421码异或。
代码实现
public static int toBinaryCode(int grayCode, int n) {
String grayCodeStr = Integer.toBinaryString(grayCode);
if (grayCodeStr.length() < n) {
// 补齐0,使得二进制编码有n位
int bitCnt = grayCodeStr.length();
for (int i = bitCnt; i < n; i++) {
grayCodeStr = "0" + grayCodeStr;
}
}
StringBuilder binary = new StringBuilder();
binary.append(grayCodeStr.charAt(0)); //最左边一位不变
for (int i = 1; i < n; i++) {
char cur = grayCodeStr.charAt(i);
char preBinary = binary.charAt(i - 1);
int tmp = (cur - '0') ^ (preBinary - '0');
if (tmp == 0) binary.append('0');
else binary.append('1');
}
return Integer.parseInt(binary.toString(), 2);
}
2.3 测试
public class Demo {
public static void main(String[] args) {
int n = 3;
int powN = (int) Math.pow(2, n);
int[] grayCode = new int[powN];
System.out.println("========= Binary to GrayCode ==========");
for (int i = 0; i < powN; i++) {
grayCode[i] = toGrayCode(i);
System.out.print(grayCode[i] + " ");
}
System.out.println();
System.out.println("========= GrayCode to Binary ==========");
for (int i = 0; i < powN; i++) {
int binary = toBinaryCode(grayCode[i], n);
System.out.print(binary + " ");
}
System.out.println();
}
public static int toGrayCode(int i) {
return i ^ (i >> 1);
}
public static int toBinaryCode(int grayCode, int n) {
String grayCodeStr = Integer.toBinaryString(grayCode);
if (grayCodeStr.length() < n) {
// 补齐0,使得二进制编码有n位
int bitCnt = grayCodeStr.length();
for (int i = bitCnt; i < n; i++) {
grayCodeStr = "0" + grayCodeStr;
}
}
StringBuilder binary = new StringBuilder();
binary.append(grayCodeStr.charAt(0)); //最左边一位不变
for (int i = 1; i < n; i++) {
char cur = grayCodeStr.charAt(i);
char preBinary = binary.charAt(i - 1);
int tmp = (cur - '0') ^ (preBinary - '0');
if (tmp == 0) binary.append('0');
else binary.append('1');
}
return Integer.parseInt(binary.toString(), 2);
}
}
可以看到二进制与格雷码之间是可以正确转换的。
参考文献
[1] https://baike.baidu.com/item/%E6%A0%BC%E9%9B%B7%E7%A0%81/6510858?fr=aladdin#5
[2] https://leetcode.com/problems/gray-code/