Код Грея и бинарные строки

Часто порядок генерации кортежей бывает важен. Разберем здесь генерацию бинарных строк таким образом, чтобы каждая следующая строка отличалась от предыдущей ровно в однои бите. Такой способ кодирования называется кодом Грея.

Например, последовательность кодов Грея для n=4n = 4

0000,0001,0011,0010,0110,0111,0101,0100,1100,1101,1111,1110,1010,1011,1001,1000\align{ 0000, \quad 0001&, \quad 0011, \quad 0010, \quad 0110, \quad 0111, \quad 0101, \quad 0100, \\ 1100, \quad 1101&, \quad 1111, \quad 1110, \quad 1010, \quad 1011, \quad 1001, \quad 1000}

Код Грея можно задать рекурсивно.

Пусть Γn\Gamma_n — последовательность кодов Грея длины nn. Тогда Γ0=ε\Gamma_0 = \varepsilon и

Γn+1=0Γn, 1ΓnR\Gamma_{n+1} = 0 \, \Gamma_n, ~ 1 \, \Gamma_n^\R

где aXa \, X означает новую последовательность, полученную путём присоединения aa перед всеми членами последовательности XX, и XRX^\R означает новую последовательность, полученную путём разворачивания всех членов последовательности XX как строк.

Поскольку Γn+1\Gamma_{n+1} начинается с 0Γn0 \, \Gamma_n, то последовательность

Γ=g(0), g(1), g(2), g(3), g(4), =0, 1, 11, 10, 110, \Gamma_\oo = g(0),~ g(1),~ g(2),~ g(3),~ g(4),~ \dotsc = 0,~ 1,~ 11,~ 10,~ 110,~ \dotsc

является перестановкой чисел N0\NN_0, задающей коды Грея по их индексам.

Последовательность кодов Грея можно определить, явно указав члены последовательности Γ\Gamma_\oo, каждый член g(j)g(j) — код Грея с индексом jj.

Пусть j=2k+rj = 2^k + r, где 0r<2k0 \le r < 2^k. Тогда можно воспользоваться рекуррентным соотношением и получить, что

g(2k+r)=2k+g(2kr1)g \bigl( 2^k + r \bigr) = 2^k + g \bigl( 2^k - r - 1 \bigr)

Отсюда можно вывести приятную формулу вычисления g(j)g(j).

Пусть двоичное представление j=(j2j1j0)2j = \bigl( \dotsm j_2 \, j_1 \, j_0 \bigr)_2, и двоичное представление g(j)=(g2g1g0)2g(j) = \bigl( \dotsm g_2 \, g_1 \, g_0 \bigr)_2. Тогда

gk=jkjk+1для всех k0g_k = j_k \oplus j_{k+1} \qquad \text{для всех}~ k \ge 0

Имеет место и обратная формула

jk=gkgk+1gk+2gk+3=i=0gk+iдля всех k0j_k = g_k \oplus g_{k+1} \oplus g_{k+2} \oplus g_{k+3} \oplus \dotsb = \bigoplus_{i=0}^\oo g_{k+i} \qquad \text{для всех}~ k \ge 0

С помощью этих двух формул можно получить способ очень легко и быстро вычислять код Грея и номер кода Грея с помощью битовой арифметики

g(j)=jj/2g(j) = j \oplus \lfloor j/2 \rfloor
j=g(j)g(j)/2g(j)/4=i=0g(j)2ij = g(j) \oplus \lfloor g(j)/2 \rfloor \oplus \lfloor g(j)/4 \rfloor \oplus \dotsb = \bigoplus_{i=0}^\oo \left\lfloor \frac{g(j)}{2^i} \right\rfloor

В коде ещё проще

unsigned int to_gray_code(unsigned int n) { return n ^ (n >> 1); } unsigned int from_gray_code(unsigned int gray) { unsigned int n = gray; while (gray >>= 1) { n ^= gray; } return n; }

На основе кодов Грея можно сделать генератор всех битовых строк фиксированной длины, при этом каждую итерацию изменяться будет только один бит в выдаваемой строке.

Нужно только понять, какой бит менять на kk-й итерации.

ρ(k)\rho(k) — позиция младшего единичного бита в kk

generator binary_tuples(int n) -> tuple[bool]: array[bool] bits = [0, 0, ..., 0] yield tuple(bits) for int i = 1; i < 2 ** n; i++: change_bit = (i ^ (i >> 1)) ^ ((i-1) ^ ((i-1) >> 1)) pos = change_bit.bit_length() - 1 bits[pos] ^= 1 yield tuple(bits)

Функции Уолша и Радемахера

Здесь безумно часто будет необходимо получить конкретный бит числа. Поэтому я сразу введу обозначения

g(k)=(g3g2g1g0)2иx=(x3x2x1x0)2g(k) = \bigl( \dotsm g_3 \, g_2 \, g_1 \, g_0 \bigr)_2 \quad\text{и}\quad x = \bigl( \dotsm x_3 \, x_2 \, x_1 \, x_0 \bigr)_2

Здесь xjx_j означает jj-й бит числа xx.

Функции Уолша — функции, введенные Джозефом Леонардом Уолшем для упрощения работы с комбинаторными объектами и их генерации.

w0(x)=1иwk(x)=(1)xmod2wk/2(x/2)для k>0w_0(x) = 1 \quad\text{и}\quad w_k(x) = (-1)^{x \bmod 2} \cdot w_{\lfloor k/2 \rfloor} ( \lfloor x/2 \rfloor ) \quad\text{для}~ k > 0

Функции Уолша классически определяют как раз через код Грея и бинарную свёртку.

wk(x)=(1)g(k)xw_k(x) = (-1) ^ {g(k) \circledast x}

Здесь g(k)xg(k) \circledast x — бинарная свёртка чисел g(k)g(k) и xx:

g(x)x=j=0gjxjg(x) \circledast x = \sum\limits_{j=0}^\oo g_j \cdot x_j

Функции Радемахера — функции, предложенные Гансом Радемахером

rk(x)=(1)xkr_k(x) = (-1)^{x_k}

Связь с кодами Грея

Из этих свойств можно получить важное соотношение между функциями Уолша и функциями Радемахера

wk(x)=j=0rj(x)gjw_k(x) = \prod\limits_{j=0}^\oo r_j(x)^{g_j}

Отсюда заключаем, что кратность rj(x)r_j(x) в wk(x)w_k(x) представляет собой jj-й бит числа g(k)g(k). Тогда

wk(x)=rρ(k)+1(x)wk1(x)для k>0w_{k}(x) = r_{\rho(k)+1}(x) \cdot w_{k-1}(x) \quad \text{для}~ k > 0

Ортогональность

И функции Уолша, и функции Радемахера ортогональны

12nx=02n1wa(x)wb(x)=[a=b]и12nx=02n1ra(x)rb(x)=[a=b]\frac{1}{2^n} \sum\limits_{x=0}^{2^n-1} w_a(x) \cdot w_b(x) = [a = b] \quad\text{и}\quad \frac{1}{2^n} \sum\limits_{x=0}^{2^n-1} r_a(x) \cdot r_b(x) = [a = b]

Функции Радемахера мультипликативны

rka+b(x)=rkab(x)r_k^{a + b}(x) = r_k^{a \oplus b}(x)

Из мультипликативности функций Радемахера следует групповой закон для функций Уолша

wa(x)wb(x)=wab(x)w_a(x) \cdot w_b(x) = w_{a \oplus b}(x)

Вообще получается, что функции Уолша образуют абелеву группу относительно поточечного умножения. Из этого факта следует более слабое, но часто встречающееся свойство wn(x)=w2k(x)wr(x)w_n(x) = w_{2^k}(x) \cdot w_r(x), где n=2k+rn = 2^k + r и 0r<2k0 \le r < 2^k.

А ещё, группа функций Уошла изоморфна натуральным числам с операцией побитового исключающего или

({wk:kN0},)(N0,)Z2\bigl( \{ w_k : k \in \N_0 \}, \cdot \bigr) \isom (\NN_0, \oplus) \isom \ZZ_2^\oo

Циклы Грея

Код Грея g()g(\cdot) является только одним из множества способов обхода всех бинарных строк с изменением только в одном бите за шаг.

Цикл Грея — последовательность бинарных строк v0,v1,v2,,v2n1v_0, v_1, v_2, \dotsc, v_{2^n-1} такая, что vkv_k отличается от v(k+1)mod2nv_{(k+1) \bmod 2^n} только в одном бите. Получается, что цикл Грея является гамильтоновым циклом в бинарном nn-мерном кубе.

Поскольку цикл Грея является гамильтоновым циклом, можно выбрать индексы так, чтобы

v0=00000v_0 = 000 \dotsm 00

Будем рассматривать битовые строки v0,v1,,v2n1v_0, v_1, \dotsc, v_{2^n-1} как числа, записанные в двоичной системе счисления.

Дельта-последовательность — последовательность чисел δk\delta_k, для которых

v(k+1)mod2n=vk2δkv_{(k+1) \bmod 2^n} = v_k \oplus 2^{\delta_k}

Дельта-последовательность показывает, какие биты надо менять на каком шаге. Например, для обычного кода Грея g()g(\cdot) дельта-последовательность определяется как δk=ρ(k+1)\delta_k = \rho(k+1), но последнее значение δ2n1=n1\delta_{2^n-1} = n-1, а не nn.

Расширение цикла Грея

Пусть α1j1α2j2αljl\alpha_1 \, j_1 \, \alpha_2 \, j_2 \dotsm \alpha_l \, j_l — дельта-последовательность для nn-битного цикла Грея, причем каждое jkj_k является одной координатой, каждое αk\alpha_k является последовательностью координат, возможно пустой, и ll нечётное. Тогда

α1(n+1)α1Rnα1j1α2nα2R(n+1)α2j2α3(n+1)α3Rnα3j3jl1αl(n+1)αlRnαl(n+1)αlRjl1αl1Rjl2α2Rj1α1Rn\alpha_1 \, (n+1) \, \alpha_1^\R \, n \, \alpha_1 \,\, j_1 \,\, \alpha_2 \, n \, \alpha_2^\R \, (n+1) \alpha_2 \,\, j_2 \,\, \alpha_3 \, (n+1) \, \alpha_3^\R \, n \alpha_3 \,\, j_3 \,\, \cdots \,\, j_{l-1} \,\, \alpha_l \, (n+1) \, \alpha_l^\R \, n \, \alpha_l \,\, (n+1) \, \alpha_l^\R \, j_{l-1} \, \alpha_{l-1}^\R \, j_{l-2} \, \cdots \, \alpha_2^\R \, j_1 \alpha_1^\R \, n

Количества переходов

Количества переходов дельта-последовательности δ0,δ1,δ2,,δ2n1\delta_0, \delta_1, \delta_2, \dotsc, \delta_{2^n-1} — кортеж (c0,c1,c2,,cn1)(c_0, c_1, c_2, \dotsc, c_{n-1}), в котором

cj=(количество раз, когда δk=j)c_j = \bigl( \text{количество раз, когда}~ \delta_k = j \bigr)

Количества переходов выражают частоту изменения битов с определенными номерами.

Можно аккуратно выбрать дельта-последовательность, чтобы в итоге получить более-менее сбалансированные количества переходов.

Наилучшее условие сбалансированности

Для любого nn существует цикл Грея с количествами переходов (c0,c1,c2,,cn1)(c_0, c_1, c_2, \dotsc, c_{n-1}), для которых

cjck2для 0j<k<n|c_j - c_k| \le 2 \quad\text{для}~ 0 \le j < k < n

Причём это условие сбалансированности является наилучшим.

Наилучшая сбалансированность. Каждое количество переходов cjc_j должно быть чётным числом, и c0+c1+c2++cn2+cn1=2nc_0 + c_1 + c_2 + \dotsb + c_{n-2} + c_{n-1} = 2^n.

Условие cjck2|c_j - c_k| \le 2 для 0j<k<n0 \le j < k < n выполняется тогда и только тогда, когда nrn-r количеств равны 2q2q и rr количеств равны 2q+22q+2, где q=2n1/nq = \lfloor 2^{n-1} / n \rfloor и r=2n1modnr = 2^{n-1} \bmod n.