Давайте представим, что у нас есть роутер. Через роутер проходит много пакетов по разным адресам. Нам интересно получить статистику, как много адресов задействовано в коммуникации. Есть пара проблем.
- Пакетов так много, что запомнить их все нельзя. Сказать ушедшему пакету «Вернись! Я все прощу,» — тоже.
- Всех возможных адресов
. Столько памяти на роутере нет.
Задача. Есть последовательность целых чисел , все числа принимают значения от
до
. Требуется в один проход посчитать количество различных чисел, используя
памяти.
Я расскажу вероятностный приближенный алгоритм Флажолета-Мартина. ТТХ алгоритма:
- использует
памяти!
- работает на любом входе;
- находит ответ, который отличается от точного не более чем в 3 раза с вероятностью
:
вероятность берется по случайным битам алгоритма.
В конце поста я расскажу, почему точные детерминированные алгоритмы требуют
Алгоритм Флажолета-Мартина
Представим, что у нас есть отрезок действительных чисел
Можно предположить, что точки разделят отрезок на меньших подотрезков примерно одинаковой длины. Если аккуратно записать матожидание расстояния и взять интеграл, мы получим
Пусть кто-то кинул случайно несколько точек на отрезок, и
Идея алгоритма Флажолета-Мартина — случайно бросить все числа последовательности на отрезок
, а затем найти расстояние от нуля до крайней левой точки. Если одинаковые числа будут всегда отображаться в одну точку, а разные независимо распределяться по отрезку, мы сможем прикинуть ответ.
2-независимые хэш-функции
Бросать числа на отрезок мы будем с помощью случайной хэш-функции.
Определение. Семейство хэш-функций называется 2-независимым, если для любых
и
Смысл определения в следующем. Зафиксируем два каких угодно ключа
Ключи — различные. Посмотрим на случайные величины
Как следствие, если взять всего один ключ , то величина
будет равномерна распределена по
.
Для примера возьмем простое число . Пусть
.
— это семейство всех линейных отображений по модулю
:
для
Поскольку
Поймем два важных момента. Во-первых, хранение такой функции обходится в
В тестовом коде я буду использовать поле Галуа . В описании далее можно считать, что у нас есть семейство хэш-функций
, где
— степень двойки. Хранение одной функции занимает
памяти.
Алгоритм
Пусть
Перед стартом, алгоритм случайно выбирает хэш-функцию
Будем бросать элементы последовательности на отрезок . Берем очередное значение
и записываем: ноль, точка,
в двоичном виде. Например, если
, то получится число
.
Обозначим через число лидирующих нулей в двоичной записи
. Пусть
. Мы знаем, что минимальное значение лежит между
и
.
Ответ алгоритма: .
def init():
h = H.sample()
z = 0
def process(a):
z = max(z, zero(h(a))
def answer():
return 2**(z + 0.5)
Анализ
вероятностью
Я планирую показать, что ответ алгоритма будет в 3 раза больше верного с вероятностью меньшей
Обозначим через множество всех различных чисел последовательности
, а
— их количество.
Для анализа алгоритма, нам потребуются два набора случайных величин.
Заметим, что вероятность
Значит, матожидание . Ограничим сверху дисперсию
Заметим, что дисперсия по величинам
Поскольку
Более того,
Теперь рассмотрим величину .
по линейности матожидания.
по линейности дисперсии для 2-независимых величин.
Пусть
Пусть
Финальный аккорд: медиана
Осталось понять как понизить ошибку. Возьмем случай, когда алгоритм выдает слишком большой ответ. Запустим алгоритм параллельно
Почему медиана так работает? По неравенству Чернова. Заведем случайную величину
Если медиана запусков алгоритма больше
, то это значит, что алгоритм хотя бы половину раз выдал ответ больший
и
. Тогда по неравенству Хефдинга-Чернова
где
Нижняя оценка для точного алгоритма
Давайте представим, что кто-то действительно придумал детерминированный алгоритм, который в один проход находит точное число различных элементов в один проход. Мы покажем, что такой алгоритм должен использовать
Возьмем множество размера
и положим его в качестве начала последовательности. Скормим эту часть алгоритму и посмотрим на его память.
Из одной только памяти алгоритма можно извлечь все множество . Если скормить в текущем состоянии число
, ответ алгоритма не изменится; если
, то увеличится на 1. Значит, каждому множеству
должно соответствовать свое уникальное состояние памяти.
Различных подмножеств из размера
примерно
. Если мы хотим каждому множеству сопоставить битовую строку, нам потребуется
Что почитать
- «Probabilistic Counting Algorithms for Data Base Applications», Flajolet, Martin, 1983, link.
- «The space complexity of approximating the frequency moments», Alon, Matias, Szegedy, 1999, link.
This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.
Комментариев нет:
Отправить комментарий