Универсальное хеширование

Универсальное хеширование позволяет генерировать случайную хеш-функцию, обеспечивающую равномерное распределение значений хешей

Формальный критерий

  • U - множество ключей
  • H - множество хеш-функций, отображающих U в {0..m1}
  • при случайном выборе хеш-функции вероятность коллизии двух ключей не превышает вероятность коллизии двух случайно выбранных значений из множества {0..m1}, 1/m

Для того, чтобы последнее было верно, количество функций hH, для которых h(k)=h(l), не должно превышать |H|/m

Матожидание времени выполнения операций

Для универсального хеширования при использовании метода цепочек время выполнения операций n любых операций равно Θ(n)

Как реализовать подобный алгоритм

Хеш-функция принимает 1 аргумент. Поскольку для реализации неудобно хранить полный набор всех хеш-функций, нужны правила, по которым хеш-функция будет параметризоваться, либо при инстанцировании, либо другим способом.

Универсальная хеш-функция параметризуется двумя случайными параметрами a,b, а также простым числом p таким, что максимальный ключ leqp1:

hab(k)=((ak+b) mod p) mod m)

Пример на псевдо-python

class UniversalHash:
    def __init__(self, max_key, m):  # конструктор
        self._m = m
        self._p = self.generate_prime_greater_than(max_key)
        self._a = random.randint(0, self.p)
        self._b = random.randint(1, self.p)

    def hash(self, key):  # self - указатель на сам объект
        return (((self._a * key + self._b)
                 % self._p)
                % self._m)

    def generate_prime_greater_than(self, max_key):
        ...
        return p

При инстанцировании hash_fn = UniversalHash(max_key, m) вычисляется параметр p и случайным образом выбираются два параметра a,b. При вызове метода hash_fn.hash(key) будут создаваться значения хешей, соответсующие требованию вероятности коллизий не более 1/m.

Фактически, класс UniversalHash описывает построение универсального класса (или множества) хеш-функций. Осталось только разобраться в генерации параметров a,b и наложенных на них ограничениях, а так же разобраться в корректности работы алгоритма.

Алгоритм построения универсального класса хеш-функций

  • p должно быть простым и максимально возможный ключ p1, как было сказано выше
  • aZp, где Zp={1..p1}, выбирается случайным образом из равномерного распределения
  • bZp, где Zp={0..p1}, аналогично a

(Z это множество целых чисел)

Например, если максимальный ключ - 32, p=37, мы выбираем случайным образом a и b из множеств {0..36}, {1..36}.

В этом случае хеш-функция равна, например,

h21,13(k)=((21k+13) mod 37) mod m)

Если размер таблицы равен 16, мы получим такие значения:

h21,13(1)=2
h21,13(2)=2
h21,13(3)=2
h21,13(4)=7
h21,13(5)=7
h21,13(6)=12
...
h21,13(32)=3

Для некоторых значений количество коллизий для конкретной хеш-функции может быть больше, для некоторых - меньше, но в среднем будет 1/m, и сейчас мы докажем это.

Доказательство универсальности

Hpm={hab:aZp,bZp} - множество универсальных хеш-функций для всех ключей меньше p и таблицы размера m

(на практике p может быть равно, например, простому числу >int32 и т.д., а m - любому размеру таблицы, так как данный метод не накладывает на размер таблиц дополнительных ограничений)

В этом множестве существует p(p1) возможных хеш-функций (т.к. есть p1 вариантов выбора a и p вариантов выбора b).

Для двух ключей k,lZp,kl, и определенной hab, "внутреннее" отображение f:ZpZp будет таким:

r=(ak+b) mod p
s=(al+b) mod p

фактически, любой ключ из {0..p1} изменяется и переходит в то же множество {0..p1}

Вычтем s из r:

rsa(kl)(mod p)

Покажем, что для любой пары k,l,kl также верно rs. Это верно, поскольку p - простое число, следовательно, a и (kl) отличны от нуля по модулю p, и их произведение также отлично от нуля по модулю p.

мы можем вычитать (kl), предполагая без потери общности, что kl>0 всегда, поскольку можем выбирать имена переменных произвольно.

Итак, коллизии для произвольной (поскольку a,b выбирались случайно и проивзольно) функции hab из множества H не могут случиться.

Далее, можно показать, что "внутреннее" отображение f:ZpZp биективно. Тогда из биективности отображения f и пары случайного равновероятного выбора a,b из Zp следует, что s,r случайно и равномерно распределенные переменные.

Каждая из возможных p(p1) пар a,b может быть найдена по значениям ключей k,l и паре s,rp) однозначно:

r=(ak+b) mod p
b=(rak) mod p

можно выразить a без использования b:

rs=a(kl)(mod p)
a=((rs)(kl)1 mod p) mod p

(kl)1 - число, обратное (kl) по модулю; то есть (kl)(kl)11(mod m)

из этого можно сделать вывод, что каждая пара a,b дает различные пары r,s.

Количество пар a,b и r,s одинаково и равно p(p1); отсюда и появляется биективность отображения из множества пар a,b в множество пар r,s.

Итак, пара r,s с равной вероятностью может быть любой из пар с различающимися по модулю p значениями, что следует из биективности отображения и характера выбора чисел a и b.

Осталось только показать, что вероятность коллизии значений после взятия модуля по m не превышает 1/m.

Эта вероятность будет равна вероятности совпадения значений r и s по модулю m: rs (mod m).
Если зафиксировать r, останется p1 возможных значений s. Из них по s, совпадающих по модулю с r, будет не больше, чем p/m1.

так происходит потому, что поскольку по модулю m может совпадать не более, чем каждое m-тое число, и надо посчитать количество возможных интеровалов размера m, округлить значение вверх и вычесть единицу (1 интеравал будет неполным)

p/m1((p+m1)/m)1=(p1)/m

Поделив это значение на количество возможных s, получим вероятность коллизии:

((p1)/m)/(p1)=1/m

Итак, было показано, что для любой пары k,l и для любой случайно выбранной функции habHpm вероятность коллизии равна 1/m, то есть множество функций H+pm является универсальным.

Применение

Этот алгоритм будет применен далее как основа алгоритма создания идеальной хеш-функции для заранее известного набора данных

Некоторые правила работы с округлением вверх и вниз

Краткий список основных формул (без доказательств):

  • x1<xxx<x+1 для действительных x
  • n/2+n/2=n для любого целого n
  • для любого действительного x0 и целых положительных a,b:
    • x/a/b=x/ab
    • x/a/b=x/ab
    • a/b(a(b1))/b
    • a/b(a+(b1))/b

</font>

в начало

In [ ]: