Скользящий хеш

Материал из testwiki
Перейти к навигации Перейти к поиску

Скользящий хеш (Шаблон:Lang-en, также кольцевой хеш) — хеш-функция, обрабатывающая вход в рамках некоторого окна. Получение значения хеш-функции для сдвинутого окна в таких функциях является дешевой операцией. Для пересчета значения требуется знать лишь предыдущее значение хеша, значение входных данных, которые остались за пределами окна, и значение данных, которые попали в окно. Другими словами, если x=h(a1a2an) представляет собой хеш последовательности a1a2an, то хеш h(a2a3anan+1) для «сдвинутой» последовательности a2a3anan+1 может быть получен с помощью легко вычислимой функции f(x,a1,an+1).

Возможность быстрого «сдвига» хеша накладывает некоторые ограничения на теоретические гарантии. В частности, показаноШаблон:Sfn, что семейства кольцевых хешей не могут быть Шаблон:Iw; максимум — универсальными или Шаблон:Iw. Впрочем, для большинства приложений достаточно универсальности (даже приблизительной).

Кольцевой хеш применяется для поиска подстроки в алгоритме Рабина — Карпа, для вычисления хешей N-грамм в текстеШаблон:Sfn, а также в программе rsync для сравнения двоичных файлов (используется кольцевая версия adler-32).

Полиномиальный хеш

В алгоритме Рабина — Карпа часто используется простой полиномиальный кольцевой хеш, построенный на операциях умножения и сложенияШаблон:SfnШаблон:Sfn:

h(a1a2an)=(a1xn1+a2xn2+a3xn3++anx0)modq.

Чтобы избежать использования целочисленной арифметики произвольной точности, используется арифметика в кольце вычетов по модулю q, умещающемуся в одно машинное слово. Выбор констант x и q очень важен для получения качественного хеша. В исходном варианте хеша предполагалось, что q должно быть случайно выбранным простым числом, а x=2.Шаблон:Sfn Но ввиду того, что алгоритм выбора случайного простого числа не такой простой, предпочитают использовать вариант хеша, в котором q является фиксированным простым числом, а x выбирается случайно из диапазона {0,1,,q1}. Дитзфелбингер и др.Шаблон:Sfn показали, что такой вариант хеша имеет те же теоретические характеристики, что и исходный. В частности, вероятность совпадения значений хешей двух различных строк a1a2an и b1b2bn не превосходит 1/nc, если a1,,an и b1,,bn представляют собой целые числа из диапазона [0,q), q>nc+1 и x выбирается действительно случайно.

Удаление старых входных символов и добавление новых производится путём прибавления или вычитания первого или последнего члена формулы (по модулю q). Для удаления члена a1xn1 хранят заранее посчитанное значение xn1modq. Сдвиг окна производится путём домножения всего многочлена h(a1a2an) на x либо делением на x (если q простое, то в кольце вычетов возможно вместо деления производить умножение на обратную величину). На практике удобнее всего полагать q=2311 или q=2611 для, соответственно, 32- и 64-битовых машинных слов (это так называемые простые числа Мерсенна). В таком случае операция взятия модуля может быть выполнена на многих компьютерах с помощью быстрых операций побитового сдвига и сложения[1]. Другой возможный выбор — значения q=2325 или q=26459, для которых тоже существуют быстрые алгоритмы взятия остатка от деления на q (при этом диапазон допустимых значений x немного сужают)Шаблон:Sfn. Частое заблуждение — полагать q=232. Существуют семейства строк, на которых хеш с q=2L будет всегда давать множество коллизий, независимо от выбора L.Шаблон:Sfn Эти и другие дальнейшие детали реализации и теоретического анализ полиномиального хеша можно найти в статье об алгоритме Рабина — Карпа.

Полиномиальный хеш над полем GF(2L)

Данный хеш похож на обычный полиномиальный хеш, но все вычисления в нём производятся в конечном поле GF(2L). Обычно L выбирается равным 64. Элементы поля — это числа 0,1,,2L1. Сложение в поле реализуется с помощью операции побитового исключающего «или» , а умножение выполняется с помощью операции ab, которая сначала Шаблон:Iw a на b, а потом берёт остаток от «беспереносного» деления результата на некоторый выбранный фиксированный элемент q{2L,2L+1,,2L+11} (беспереносным делением здесь названа операция, обратная беспереносному умножению). Элемент q=2i1+2i2++2ik должен быть выбран так, что L=i1>i2>>ik0 и xi1+xi2++xi0 — это неприводимый многочлен над полем GF(2) (на поле GF(2L) часто смотрят как на множество многочленов над полем GF(2) по модулю произвольного неприводимого многочлена степени L). Например, можно положить q=264+24+23+2+1Шаблон:Sfn. Тогда хеш вычисляется следующим образомШаблон:Sfn:

h(a1a2an)=(a1xn1)(a2xn2)(an1x)an,

где x — это случайно выбранное на этапе инициализации хеша число из диапазона {0,1,,2L1}, а xm — это короткая запись для xxx, где x повторён m раз. С помощью основной теоремы алгебры можно показать, что вероятность коллизии хешей двух различных строк длины n не превосходит n/2L. ПоказаноШаблон:Sfn, что на современных процессорах Intel и AMD вся необходимая для хеша арифметика над полем GF(2L) может быть эффективно вычислена с помощью инструкций из расширения Шаблон:Iw.

Хеш циклическими полиномами (Buzhash)

Пусть h — какой-то хеш, который отображает символы a1,,an хешируемой строки в L-битовые числа (обычно L=32 или L=64). Хеш циклическими полиномами определяется следующим образомШаблон:Sfn:

h(a1a2an)=sn1(h(a1))sn2(h(a2))s(h(an1))h(an),

где  — это операция побитового исключающего «или», а si(x) — это операция циклического сдвига L-битового числа x на i битов влево. Несложно показать, что данный хеш кольцевой:

h(a2a3an+1)=s(h(a1a2an))sn(h(a1))h(an+1).

Главное преимущество этого хеша в том, что он использует только быстрые побитовые операции доступные на многих современных компьютерах. Качество хеша напрямую зависит от выбора функции h. Лемире и КасерШаблон:Sfn доказали, что если функция h выбирается случайно из семейства Шаблон:Iw, то вероятность совпадения хешей двух различных строк длины n не превосходит 1/2Ln+1. Это накладывает определённые ограничения на диапазон задач, в которых данный хеш может использоваться. Во-первых, длина хешируемых строк должна быть меньше L. Для алгоритмов хеширования общего назначения это условие может быть проблемой, но, например, для хеширования n-грамм, где n обычно не превосходит 16, такое ограничение является естественным (в случае n-грамм роль символов играют отдельные лексемы текста). Во-вторых, выбор семейства независимых функций h в некоторых случаях тоже может быть проблемой. Для байтового алфавита свойством независимости обладает семейство функций h, закодированных таблицей из 256-и различных случайных L-битовых чисел (выбор функции — это заполнение таблицы). Для хеширования n-грамм можно присваивать различные случайные L-битовые числа различным лексемам (обычно число разных лексем в таких задачах относительно невелико) и такое семейство хеш-функций h тоже имеет свойство независимости.

Хеш Рабина

Данный хеш применим только в специальном случае, когда символы хешируемой строки a1a2an суть числа 0 и 1. Идея хеша в том, чтобы смотреть на входную строку a1a2an как на многочлен A(x)=a1xn1a2xn2an1xanx0 над полем GF(2), а сам хеш представляет собой взятие остатка от деления A(x) на случайно выбранный на этапе инициализации хеша неприводимый многочлен P(x) степени L над полем GF(2). По существу это та же процедура, что используется в CRC. Рассмотрим её более подробно.

Результат хеширования строки a1a2an — это последовательность битов bL1bL2b0. Число L выбирается простымШаблон:Sfn и достаточно большим, но так чтобы последовательность bL1bL2b0 умещалась в одно машинное слово (обычно берут L=31 или L=61Шаблон:Sfn). Пусть P(x)=pLxLpL1xL1p1xp0 представляет собой некоторый неприводимый многочлен степени L над полем GF(2). Обозначим через p соответствующее число с битовым представлением pLpL1p0. Хеш-функция h(a1a2an) определяется как число с битовым представлением bL1bL2b0, таким что многочлен B(x)=bL1xL1bL2xL2b1xb0 является остатком от деления многочлена A(x)=a1xn1a2xn2an1xan на многочлен P(x), то есть B(x)=A(x)modP(x).

Несмотря на весьма запутанное определение, хеш Рабина довольно просто реализуем (если неприводимый многочлен P(x) уже найден). Вычисления опираются на такое несложное наблюдение: если число b с битовым представлением bL1bL2b0 кодирует многочлен B(x)=bL1xL1bL2xL2b1xb0, то число sh(b) кодирует многочлен xB(x), где sh(b) обозначает операцию побитового сдвига числа b на один бит влево с замещением младшего бита нулём (не путать с циклическим сдвигом s, определённым выше!). Пусть b=h(a1a2ai), и bL1bL2b0 — это битовое представление b. Тогда h(a1a2aiai+1) вычисляется следующим образом:

sh(b)ai+1, если bL1=0,
sh(b)pai+1, если bL1=1.

Хеш является кольцевым. Пусть b=h(a1a2an) и bL1bL2b0 — это битовое представление b. Хеш h(a2a3anan+1) вычисляется следующим образомШаблон:Sfn:

sh(b)an(a1c), если bL1=0,
sh(b)pan(a1c), если bL1=1,

где c — это L-битовое число, битовое представление которого соответствует многочлену xnmodP(x). Число c вычисляют заранее при инициализации хеша строки длины n.

Главная сложность — случайным образом выбрать неприводимый многочлен P(x) степени L. РабинШаблон:Sfn описал эффективный алгоритм, позволяющий это сделать, и доказал, что вероятность коллизии хешей двух различных строк длины n при случайном выборе P(x) не превосходит n/2L.

Отметим, что данный хеш часто путают с полиномиальным хешем из-за схожей области применения, рассмотрения многочленов и общего автора.

Ссылки

Примечания

Шаблон:Примечания

Литература