🔀

格雷编码

相邻两个数,只许翻一个开关——防"读串"的聪明计数法

💡 打个比方

一排机械开关表示数字:二进制数 3 是 011,下一个数 4 是 100——一下要同时翻 3 个开关!但机械开关不可能完全同时动作:可能先翻一个再翻另一个,中途会出现 001、000 这样的"中间状态",读的人正好在这一瞬间来看,就读出了错误的数。

格雷码(Gray Code)是一套管开关的"重新编号法":保证相邻两个编码之间,恰好只有一个开关状态不同(术语:汉明距离 = 1)。这样任何时刻最多只有一个开关在动,中间状态至多两种,而且都好防——老式的编码器、电梯楼层显示、数字通信都靠它防错。

格雷码:一种二进制编码,任意相邻两个编码只差 1 位。生成口诀:格雷码 = 二进制 x 异或 x 右移 1 位,即 g = x ^ (x >> 1);还原:从高位起逐位"当前位 = 格雷位 异或 已还原的高位"。

🖼️ 大图看明白

左边是"开关故障"现场,右边是 3 位二进制 vs 格雷码全表——仔细看格雷码一列,上下相邻只变 1 位。

为什么要格雷码? 二进制 3 → 4:011 → 100 0 1 1 → 1 0 0 3 个开关同时翻 ⚡ 中途可能读出 000、111 等错值 格雷码 3 → 4:010 → 110 0 1 0 → 1 1 0 只有 1 个开关动 ✓ 中间状态好防 注意:格雷码的"3 → 4"和二进制表示的 数不同步,它是"另一套编号"。 3 位对照表(相邻格雷码只差 1 位) 十进制 二进制 格雷码 0 000 000 1 001 001 2 010 011 3 011 010 4 100 110 5 101 111 6 110 101 7 111 100 橙框:二进制 3→4 翻 3 位;格雷码 010→110 只翻 1 位

🧩 子任务 1:二进制计数爱"群翻"

🎯 小目标:会数两个编码的"差异位数"(汉明距离),说清格雷码的价值

两个等长 01 串,不同位的个数叫汉明距离(Hamming Distance)。数一数右边表格里相邻两行的二进制:011 → 100 差 3 位;000→001、001→010 各差 1 位,010→011 差 1 位……二进制相邻数经常一次翻 2~3 位(位数越多越频繁)。

机械/电路世界里,"多位同时翻"是噩梦:各位翻动总有先后,中间会冒出一串谁也不是的"假编码"。格雷码的规定只有一条:相邻编码汉明距离 = 1——一次只动一个开关,过渡态只有"旧的还没翻完"和"新的已经翻完"两种,都能识别、不会读串。

考点:格雷码的相邻编码汉明距离为 1(单选高频)。它不要求"编码大小和数值大小同步"(看表:格雷码 010 代表 3、110 代表 4,数字反而"乱"了),只要求相邻差 1 位。
✅ 小检查:5(101)和 6(110)的二进制汉明距离是几?它们的格雷码 111 和 101 汉明距离是几?
看看答案
二进制 101 vs 110:第 2、3 位不同,距离 = 2。格雷码 111 vs 101:只有第 2 位不同,距离 = 1。同样"相邻的数",格雷码永远只差 1 位。

🧩 子任务 2:格雷码表怎么造?(反射法)

🎯 小目标:会用"镜像反射"从 1 位推到 2 位、3 位格雷码表

格雷码有漂亮的递归构造,像照镜子:

  1. 1 位:0,1。
  2. 2 位:把 1 位表前面加 0得到上半(00,01);再把 1 位表倒序、前面加 1得到下半(11,10)。合起来:00,01,11,10。中间接缝 01→11 只差 1 位 ✓。
  3. 3 位:同样操作——上半 = 2 位表加 0:000,001,011,010;下半 = 2 位表倒序加 1:110,111,101,100。合起来:000,001,011,010,110,111,101,100。

为什么倒序?上半的最后一个(如 2 位时的 01)和下半的第一个必须只差 1 位:上半以"0 + X"结尾,下半若正着来会以"1 + X"开头——差 1 位,但倒序后下半第一个是"1 + X 的镜像首项",接缝处恰好只翻最高位。照镜子 + 倒序,是格雷码的构造灵魂。

记忆:n 位格雷码 = [0 + (n−1)位表] 接 [1 + (n−1)位表倒序]。2ⁿ 个编码,相邻差 1 位,首尾(最后一个和第一个,若看环形)也差 1 位。
✅ 小检查:用反射法写出 2 位格雷码表的"下半"部分(倒序 + 加 1)。
看看答案
1 位表倒序 = 1,0;前面加 1 → 11,10。所以下半是 11、10,完整 2 位表 = 00,01,11,10。

🧩 子任务 3:互转公式(二 → 格、格 → 二)

🎯 小目标:能手算两种互转,记住口诀「格雷 = 二进制 异或 二进制右移 1 位」

二 → 格只有一行:把二进制右移 1 位,再和原数做异或:g = x ^ (x >> 1)。可以逐位理解:格雷码的第 i 位 = 二进制的第 i 位 异或 它左边那位(最高位没有左边,照抄)。手工推两个:

   二进制    右移1位     异或结果(=格雷码)
    011    ^   001      =   010     (3 → 010)
    100    ^   010      =   110     (4 → 110)

对照大图里的表:3 → 010 ✓、4 → 110 ✓,公式和表完全对得上。

格 → 二要"顺藤摸瓜":从最高位开始,最高位照抄;往下的每一位 = 格雷码该位 异或 已经还原出来的上一位。注意:是"已经还原的二进制位",不是格雷码的上一位。推两个:

  格雷 110:  最高位照抄 1
              第二位 = 1 ⊕ 1(已还原高位) = 0
              第三位 = 0 ⊕ 0(已还原上一位) = 0
              → 二进制 100 = 4 ✓(和表里 4 → 110 互逆)
  格雷 101:  最高位照抄 1
              第二位 = 0 ⊕ 1 = 1
              第三位 = 1 ⊕ 1 = 0
              → 二进制 110 = 6 ✓
为什么公式长这样:二 → 格时,每一位都"借"了左边一位的信息(异或进去),所以信息没有丢——只是"压扁"了。还原就是把借的信息一位一位还回去,从高位往下,一步都不能乱序。
✅ 小检查:① 把二进制 6(110)转成格雷码;② 格雷码 100 对应哪个十进制数?
看看答案
① 110 ^ 011 = 101,所以 6 的格雷码是 101(对照表 6 → 101 ✓)。② 100:最高位照抄 1;第二位 = 0⊕1 = 1;第三位 = 0⊕1 = 1 → 111 = 7。对照表最后一行 7 → 100 ✓。

💻 完整代码

两个函数搞定互转的两个方向,再加一个"往返测试":0 到 999 每个数都转一圈再转回来,全对才算没写错。

// 格雷码:互转 + 往返测试
#include <bits/stdc++.h>
using namespace std;

int toGray(int x) {
    // 二 → 格:和"右移 1 位的自己"异或
    return x ^ (x >> 1);
}

int toBin(int g) {
    // 格 → 二:当前位 = 格雷位 异或 已还原的高位
    int b = 0;
    for (int i = 31; i >= 0; i--) {
        int gbit = (g >> i) & 1;     // 格雷码第 i 位
        int bbit = gbit ^ (b & 1);   // 二进制第 i 位
        b = (b << 1) | bbit;         // 接到 b 的尾巴上
    }
    return b;
}

void print3(int x) {
    // 把 x 的低 3 位逐位打出来
    printf("%d%d%d", (x >> 2) & 1, (x >> 1) & 1, x & 1);
}

int main() {
    // 1) 打印 3 位对照表:十进制 / 二进制 / 格雷码
    for (int i = 0; i < 8; i++) {
        printf("%d: 二进制=", i); print3(i);
        printf(" 格雷="); print3(toGray(i));
        printf("\n");
    }
    // 2) 往返测试:格 → 二 必须转回原数
    for (int i = 0; i < 1000; i++) {
        if (toBin(toGray(i)) != i) {
            printf("往返失败: %d\n", i);
            return 1;
        }
    }
    cout << "往返测试 0~999 通过" << endl;
    return 0;
}
易错点:① toBin 里异或的是已还原出来的二进制位,不是格雷码的高位——拿错对象全盘皆错。② toBin 的循环必须从高位到低位(31 → 0),顺序不能反。③ 打印二进制别偷懒用 printf("%d")(那打的是十进制),要用 (x>>2)&1 这样的方式逐位取。

✋ 你来试试

第 1 题:5(二进制 101)的格雷码是几?
111。101 ^ 010 = 111(5>>1 = 2 = 010)。对照表核对:5 → 111 ✓。
第 2 题:格雷码 101,对应的十进制数是几?
6。逐位还原:最高位照抄 1;第二位 = 0⊕1 = 1;第三位 = 1⊕1 = 0 → 110 = 6。对照表核对:6 → 101 ✓。
第 3 题:为什么老式编码器、电梯楼层这类机电系统偏爱格雷码?
因为相邻格雷码只差 1 位:切换瞬间只有一个开关在动,任何时刻读到的至多是"旧码"或"新码"两种,都不是假值;而二进制 3→4 要翻 3 位,中途可能读出 000 这种谁也不是的假值,容易出错。
🎯 一句话记住:格雷码 = 二进制 ^ (二进制右移 1 位);相邻编码只翻 1 位,过渡不读串;还原从高位起"异或已还原的高位"。