Функция сопряжения Кантора действительно одна из лучших, учитывая ее простоту, скорость и эффективность использования пространства, но кое-что еще лучше опубликовано в Wolfram Мэтью Шудзиком, здесь . Ограничение функции сопряжения Кантора (относительно) состоит в том, что диапазон закодированных результатов не всегда остается в пределах 2N
битового целого числа, если входные данные являются N
двухбитными целыми числами. То есть, если мои входные данные представляют собой 16
двухбитные целые числа в диапазоне от 0 to 2^16 -1
, то 2^16 * (2^16 -1)
возможны комбинации входных данных, поэтому, в соответствии с очевидным принципом Pigeonhole , нам нужен выход по крайней мере такого размера 2^16 * (2^16 -1)
, который равен 2^32 - 2^16
или, другими словами, карте32
битовые числа должны быть выполнимыми в идеале. Это не может иметь мало практического значения в мире программирования.
Функция сопряжения Кантора :
(a + b) * (a + b + 1) / 2 + a; where a, b >= 0
Отображение для двух максимально 16-битных целых чисел (65535, 65535) будет 8589803520, которое, как вы видите, не может быть вписано в 32 бита.
Введите функцию Шудзика :
a >= b ? a * a + a + b : a + b * b; where a, b >= 0
Отображение для (65535, 65535) теперь будет 4294967295, которое, как вы видите, является 32-битным (от 0 до 2 ^ 32 -1) целым числом. Вот где это решение идеально, оно просто использует каждую точку в этом пространстве, поэтому ничто не может быть более эффективным.
Теперь, учитывая тот факт, что мы обычно имеем дело со знаковыми реализациями чисел различных размеров в языках / инфраструктурах, давайте рассмотрим signed 16
битовые целые числа, начиная от -(2^15) to 2^15 -1
(позже мы увидим, как расширить даже выходной поток, чтобы охватить диапазон со знаком). Так как a
и b
должны быть положительными, они варьируются от 0 to 2^15 - 1
.
Функция сопряжения Кантора :
Отображение для двух максимальных 16-разрядных целых чисел со знаком (32767, 32767) будет 2147418112, что немного меньше максимального значения для 32-разрядного целого числа со знаком.
Теперь функция Шудзика :
(32767, 32767) => 1073741823, намного меньше ..
Давайте учтем отрицательные целые числа. Это выходит за рамки первоначального вопроса, который я знаю, но просто помогаю будущим посетителям.
Функция сопряжения Кантора :
A = a >= 0 ? 2 * a : -2 * a - 1;
B = b >= 0 ? 2 * b : -2 * b - 1;
(A + B) * (A + B + 1) / 2 + A;
(-32768, -32768) => 8589803520, то есть Int64. 64-битный выход для 16-битных входов может быть таким непростительным !!
Функция Шудзика :
A = a >= 0 ? 2 * a : -2 * a - 1;
B = b >= 0 ? 2 * b : -2 * b - 1;
A >= B ? A * A + A + B : A + B * B;
(-32768, -32768) => 4294967295, который является 32-битным для диапазона без знака или 64-битным для диапазона со знаком, но все же лучше.
Теперь все это пока результат всегда был положительным. В мире со знаком будет еще больше экономии места, если мы сможем перевести половину вывода на отрицательную ось . Вы можете сделать это так для Шудзика:
A = a >= 0 ? 2 * a : -2 * a - 1;
B = b >= 0 ? 2 * b : -2 * b - 1;
C = (A >= B ? A * A + A + B : A + B * B) / 2;
a < 0 && b < 0 || a >= 0 && b >= 0 ? C : -C - 1;
(-32768, 32767) => -2147483648
(32767, -32768) => -2147450880
(0, 0) => 0
(32767, 32767) => 2147418112
(-32768, -32768) => 2147483647
Что я делаю: применив вес 2
к входам и пройдя через функцию, затем разделю выходной сигнал на два и перенесу некоторые из них на отрицательную ось, умножив на -1
.
См. Результаты, для любого ввода в диапазоне числа 16
битов со знаком, выход лежит в пределах 32
целого числа битов со знаком, что здорово. Я не уверен, как сделать то же самое для функции сопряжения Кантора, но не пытался так сильно, как это не так эффективно. Кроме того, чем больше вычислений, связанных с функцией сопряжения Кантора, тем медленнее и ее .
Вот реализация C #.
public static long PerfectlyHashThem(int a, int b)
{
var A = (ulong)(a >= 0 ? 2 * (long)a : -2 * (long)a - 1);
var B = (ulong)(b >= 0 ? 2 * (long)b : -2 * (long)b - 1);
var C = (long)((A >= B ? A * A + A + B : A + B * B) / 2);
return a < 0 && b < 0 || a >= 0 && b >= 0 ? C : -C - 1;
}
public static int PerfectlyHashThem(short a, short b)
{
var A = (uint)(a >= 0 ? 2 * a : -2 * a - 1);
var B = (uint)(b >= 0 ? 2 * b : -2 * b - 1);
var C = (int)((A >= B ? A * A + A + B : A + B * B) / 2);
return a < 0 && b < 0 || a >= 0 && b >= 0 ? C : -C - 1;
}
Поскольку промежуточные вычисления могут превышать пределы 2N
целого числа со знаком, я использовал 4N
целочисленный тип (последнее деление на 2
возвращает результат в 2N
).
Ссылка, которую я предоставил на альтернативное решение, хорошо изображает график функции, использующей каждую точку в пространстве. Удивительно видеть, что вы можете уникально закодировать пару координат в одно число обратимо! Волшебный мир чисел !!