跳转至

格雷码

格雷码(Gray code)是一个二进制数系,其中两个相邻数的二进制位只有一位不同。一个例子,\(3\) 位二进制数的格雷码序列为

\[ 000,001,011,010,110,111,101,100 \]

注意序列的下标我们以 \(0\) 为起点,也就是说 \(G(0)=000,G(4)=110\)

两种构造方法

手动构造

\(k\) 位的格雷码可以通过以下方法构造。我们从全 \(0\) 格雷码开始,按照下面策略:

  1. 翻转最低位得到下一个格雷码,(例如 \(000\to 001\));
  2. 把最右边的 \(1\) 的左边的位翻转得到下一个格雷码,(例如 \(001\to 011\));

交替按照上述策略生成 \(2^{k-1}\) 次,可得到 \(k\) 位的格雷码序列。

代码实现
vector<int> grayCode(int n) {
    vector<int> data = {0};
    int x = 0;
    int N = pow(2, n) - 1;
    for (int i = 0; i < N; i++) {
        if ((i & 1) == 0) {
            x = (x ^ 1);
        } else {
            int k = 0;
            while (!(x & (1 << k))) k++;
            x = x ^ (1 << (k+1));
        }
        data.push_back(x);
    }
    return data;
}

镜像构造

\(k\) 位的格雷码可以从 \(k-1\) 位的格雷码以上下镜射后加上新位的方式快速得到,如下图:

\[ \begin{matrix} k=1\\ 0\\ 1\\\\\\\\\\\\\\ \end{matrix} \to \begin{matrix}\\ \color{Red}0\\\color{Red}1\\\color{Yellow}1\\\color{Yellow}0\\\\\\\\\\ \end{matrix} \to \begin{matrix} k=2\\ {\color{Red}0}0\\{\color{Red}0}1\\{\color{Yellow}1}1\\{\color{Yellow}1}0\\\\\\\\\\ \end{matrix} \to \begin{matrix}\\ \color{Red}00\\\color{Red}01\\\color{Red}11\\\color{Red}10\\\color{Yellow}10\\\color{Yellow}11\\\color{Yellow}01\\\color{Yellow}00 \end{matrix} \to \begin{matrix} k=3\\ {\color{Red}0}00\\{\color{Red}0}01\\{\color{Red}0}11\\{\color{Red}0}10\\{\color{Yellow}1}10\\{\color{Yellow}1}11\\{\color{Yellow}1}01\\{\color{Yellow}1}00 \end{matrix} \]

假定 \(k\) 位格雷码序列长度为 \(n\),我们将这 \(k\) 位的格雷序列进行翻转,并追加到原有序列的尾部,得到长度为 \(2*n\) 的序列,此时新序列的前后两部分均为合法的格雷码。

考虑如何进行解决衔接点的合法性:我们可以对于序列的后半(翻转而来)的部分中的每个数进行「尾部」追加 \(1\) 的操作,确保链接点的两个数只有有一位二进制位不同,同时并不影响前后两半部分的合法性。

而且由于后半部分本身是由前半部分翻转而来,序列中的第一个数和最后一个数原本为同一个值,经过追加 \(1\) 的操作之后,首尾两个数的二进制表示只有一位不同,整个序列的合法性得以保证。

代码实现
vector<int> grayCode(int n) {
    vector<int> data = {0};
    int k = 0;
    while (k++ < n) {
        int m = data.size();
        int x = 1 << (k-1);
        for (int i = m - 1; i >= 0; i--) {
            data.push_back(data[i] + x);
        }
    }
    return data;
}

参考

格雷码 - OI Wiki (oi-wiki.org) 89. 格雷编码 - 力扣(Leetcode)