格雷码
格雷码简介
在一组数的编码中,若任意两个相邻的编码只有一位二进制数不同,则称这种编码为格雷码(Gray Code),另外由于最大数和最小数之间也仅有一位数不同,即”首尾相连”,因此又成循环码、反射码。
格雷码编码形式
十进制数 | 4位自然二进制码 | 4位典型格雷码 | 十进制余三格雷码 | 十进制空六格雷码 | 十进制跳六格雷码 | 步进码 |
---|---|---|---|---|---|---|
0000 | 0000 | 0010 | 0000 | 0000 | 00000 | |
1 | 0001 | 0001 | 0110 | 0001 | 0001 | 00001 |
2 | 0010 | 0011 | 0111 | 0011 | 0011 | 00011 |
……
表中典型格雷码具有代表性。若不做特别说明,格雷码就是指典型格雷码,它可从自然二进制码转换而来。(其他格雷码怎么转换我也不知道…)
为什么要使用格雷码
雷码是一种具有反射特性和循环特性的单步自补码,其循环和单步特性消除了随机取数时出现重大错误的可能,其反射和自补特性使得对其进行求反操作也非常方便,所以,格雷码属于一种可靠性编码,是一种错误最小化的编码方式,因此格雷码在通信和测量技术中得到广泛应用。
格雷码属于可靠性编码,是一种错误最小化的编码方式。因为,虽然自然二进制码可以直接由数/模转换器转换成模拟信号,但在某些情况,例如从十进制的3转换为4时二进制码的每一位都要变,能使数字电路产生很大的尖峰电流脉冲。而格雷码则没有这一缺点,它在相邻位间转换时,只有一位产生变化。它大大地减少了由一个状态到下一个状态时逻辑的混淆。由于这种编码相邻的两个码组之间只有一位不同,因而在用于方向的转角位移量-数字量的转换中,当方向的转角位移量发生微小变化(而可能引起数字量发生变化时,格雷码仅改变一位,这样与其它编码同时改变两位或多位的情况相比更为可靠,即可减少出错的可能性。
在数字系统中,常要求代码按一定顺序变化。例如,按自然数递增计数,若采用8421码,则数0111变到1000时四位均要变化,而在实际电路中,4位的变化不可能绝对同时发生,则计数中可能出现短暂的其它代码(1100、1111等)。在特定情况下可能导致电路状态错误或输入错误。使用格雷码可以避免这种错误。
格雷码是一种绝对编码方式,典型格雷码是一种具有反射特性和循环特性的单步自补码,它的循环、单步特性消除了随机取数时出现重大误差的可能,它的反射、自补特性使得求反非常方便。
由于格雷码是一种变权码,每一位码没有固定的大小,很难直接进行比较大小和算术运算,也不能直接转换成液位信号,要经过一次码变换,变成自然二进制码,再由上位机读取。
典型格雷码是一种采用绝对编码方式的准权码,其权的绝对值为2^i-1(设最低位i=1)。
格雷码的十进制数奇偶性与其码字中1的个数的奇偶性相同。
应用
格雷氏编码与相位移在三维曲面量测:利用格雷码投射在微型曲面做量测 一个非接触式、投影的方法光学测量。
在化简逻辑函数时,可以通过按格雷码排列的卡诺图来完成。
角度传感器:汽车制动系统有时需要传感器产生的数字值来指示机械位置。如图是编码盘和一些触点的概念图,根据盘转的位置,触点产生一个3位二进制编码,共有8个这样的编码。盘中暗的区域与对应的逻辑1的信号源相连;亮的区域没有连接,触点将其解释为逻辑0。使用格雷码对编码盘上的亮暗区域编码,使得其连续的码字之间只有一个数位变化。这样就不会因为器件制造的精确度有限,而使得触点转到边界位置而出现错误编码。
九连环问题:中国的古老益智玩具九连环有着和格雷码完全相同的数学模式,外国一款名为spin out的玩具也是运用相同的数学模式。智力玩具九连环的状态 变化符合格雷码的编码规律,汉诺塔的解法也与格雷码有关。九连环中的每个环都有上下两种状态,如果把这两种状态用0/1来表示的话,这个状态序列就会形成一种循环二进制编码(格雷码)的序列。所以解决九连环问题所需要的状态变化数就是格雷码111111111所对应的十进制数341。
二进制格雷码的生成
问题:产生n位元的所有格雷码字符串表示
格雷码(Gray Code)是一个数列集合,每个数使用二进制位来表示,假设使用n位元来表示每个数字,任意连个数之间只有一个位元值不同。
例如一下为3位元的格雷码:000,001,011,010,110,111,101,100。
如何要产生n位元的格雷码,那么格雷码的个数为2^n。
直接排列
生成二进制格雷码方式1:以二进制位0的格雷码为第0项,第一项改变最右面的位元,第二项改变右起第一个位1的委员的左边位元,第三、第四项方法同第一、第二项,循环反复,即可排列出n个位元的格雷码。
用一个例子来证明:
假设产生3位元的格雷码,原始值位 000
改变最右面的位元值:001
改变右起第一个为1的位元的左面的位元:011
改变最右面的位元值:010
改变右起第一个位1的位元的左面的位元:110
改变最右面的位元值:111
改变右起第一个位1的位元的左面的位元:101
改变最右面的位元值:100
代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53/**
* 直接获取格雷码
* @param n
* @return
*/
public List<StringBuffer> directGetCode(int n){
List<StringBuffer> result = new ArrayList<>();
// 获取第一位 000
StringBuffer startSB = new StringBuffer();
//记录上一个数的二进制格雷码
StringBuffer lastSB = new StringBuffer();
for (int i = 0; i < n; i++) {
startSB.append(0);
lastSB = startSB;
}
result.add(startSB);
int count = 1<<n;
for (int i = 1; i < count; i++) {
StringBuffer sb = new StringBuffer();
if(i%2 == 1){
String preStr = lastSB.substring(0, lastSB.length() - 1);
sb.append(preStr);
String endStr = lastSB.substring(lastSB.length() - 1);
if(endStr.equals("0")){
sb.append(1);
}else{
sb.append(0);
}
result.add(sb);
lastSB = sb;
}else{
for (int j = lastSB.length()-1; j >=0 ; j--) {
char c = lastSB.charAt(j);
if("1".equals(String.valueOf(c))){
String preStr = lastSB.substring(0, j-1);
sb.append(preStr);
String midStr = lastSB.substring(j-1,j);
if(midStr.equals("0")){
sb.append(1);
}else{
sb.append(0);
}
String endStr = lastSB.substring(j);
sb.append(endStr);
result.add(sb);
lastSB = sb;
break;
}
}
}
}
return result;
}
镜像排列
生成二进制格雷码方式2:n位元的格雷码可以从n-1位的格雷码以上下镜像后加上新位元的方式快速得到。
如果按照直接排列规则来生成格雷码,是没有问题的,但是这样做太复杂了。如果仔细观察格雷码的结构,我们会有以下发现:
1、除了最高位(左边第一位),格雷码的位元完全上下对称(看下面列表)。比如第一个格雷码与最后一个格雷码对称(除了第一位),第二个格雷码与倒数第二个对称,以此类推。
2、最小的重复单元是 0 , 1。
000
001
011
010
110
111
101
100
所以,在实现的时候,我们完全可以利用递归,在每一层前面加上0或者1,然后就可以列出所有的格雷码。
比如:
第一步:产生 0, 1 两个字符串。
第二步:在第一步的基础上,正向每一个字符串都分别加上0,然后反向迭代每一个字符串都加上1,但是每次只能加一个,所以得做两次。这样就变成了 00,01,11,10 (注意对称)。
第三步:在第二步的基础上,再给每个字符串都加上0和1,同样,每次只能加一个,这样就变成了 000,001,011,010,110,111,101,100。这样就把3位元格雷码生成好了。
如果要生成4位元格雷码,我们只需要在3位元格雷码上再加一层0,1就可以了: 0000,0001,0011,0010,0110,0111,0101,0100,1100,1101,1110,1010,0111,1001,1000.
也就是说,n位元格雷码是基于n-1位元格雷码产生的。
代码实现
1 | /** |
利用二进制转换
二进制码转换成二进制格雷码,实际上是保留二进制最高位作为格雷码的最高位,而次高位格雷码为二进制码的的高位与次高位相异或,格雷码其余各位与次高位求发类似。
Note: 这样做可行的原因,是因为二进制码每次+1时最多只有一个相邻的两个bit对的异或值会发生改变。
公式表示:G:格雷码 B:二进制码
整个数G(N) = (B(n) >> 1) ^ B(n)
公式原因:1. 任何数右移一位后,第一位都为0
1或0 与 0 异或后都是原来的数。
代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19/**
* 通过二进制转换成格雷码
* @param n
*/
public void getGredCode(int n){
List<StringBuffer> result = new ArrayList<>();
int count = 1 << n;
for (int i = 0; i < count; i++) {
int grayCode = (i >> 1) ^ i;
System.out.println(num2Binary(grayCode, n));
}
}
public String num2Binary(int num, int bitNum){
String ret = "";
for(int i = bitNum-1; i >= 0; i--){
ret += (num >> i) & 1;
}
return ret;
}
格雷码转二进制码
二进制格雷码转换成二进制码,其法则是保留格雷码的最高位作为自然二进制码的最高位,而次高位自然二进制码为高位自然二进制码与次高位格雷码相异或,而自然二进制码的其余各位与次高位自然二进制码的求法相类似。
代码实现
1 | public void getBinaryCode(int n){ |
相关Leetcode题目
https://leetcode-cn.com/problems/circular-permutation-in-binary-representation/ 循环码排列
代码实现
1 | class Solution { |