Ищите комплексную реализацию с небольшим объемом памяти

9

Я ищу реализацию заданного типа данных. То есть мы должны

  • поддерживать динамическое подмножество S (размера n ) из юниверса U={0,1,2,3,,u1} размера u с
  • операции insert(x)(добавить элемент xв S ) и find(x)(проверяет, xявляется ли элемент членом S ).

Я не забочусь о других операциях. Для ориентации в приложениях, с которыми я работаю, имеем u1010 .

Мне известны реализации, которые обеспечивают обе операции за время O(1) , поэтому я больше всего беспокоюсь о размере структуры данных. Я ожидаю миллиарды записей, но хочу избежать обмена как можно больше.

Я готов пожертвовать временем выполнения, если это необходимо. Амортизируемое время выполнения O(logn) - это то, что я могу допустить; ожидаемое время выполнения или время выполнения в ω(logn) недопустимо.

У меня есть идея, что если S можно представить как объединение диапазонов [xmin, xmax], то мы сможем сэкономить на размере хранилища с ценой некоторого снижения производительности. Также возможны некоторые другие шаблоны данных, например [0, 2, 4, 6].

Не могли бы вы указать мне на структуры данных, которые могут сделать что-то подобное?

HEKTO
источник
Как число элементов входит в картинку? То есть, что произойдет, если элемент будет вставлен и уже есть n ? nn
vonbrand
@vonbrand - nразмер набора S. Он может увеличиваться с каждым insertили может оставаться неизменным, если элемент xуже находится в наборе.
HEKTO
3
Можете ли вы принять небольшую вероятность ложных срабатываний? Если это так, фильтр Блума может быть идеальным: en.wikipedia.org/wiki/Bloom_filter
Джо
1
@AlekseyYakovlev, уровень ложных срабатываний фильтра Блума не имеет ничего общего с размером юниверса (только с количеством хеш-функций , размером структуры данных m и количеством элементов n ), но если n действительно близко к u (скажем, u = n c для небольшой константы c ), вам будет трудно справиться лучше, чем простой битовый вектор, я думаю, с только c n полными битами пространства. kmnnuu=ncccn
Джо

Ответы:

8

Ответ Джо очень хорош и дает вам все важные ключевые слова.

Вы должны знать, что сжатые исследования структуры данных все еще находятся на ранней стадии, и многие результаты в значительной степени теоретические. Многие из предложенных структур данных довольно сложны для реализации, но большая часть сложности связана с тем, что вам необходимо поддерживать асимптотическую сложность как по размеру юниверса, так и по количеству хранимых элементов. Если один из них является относительно постоянным, то большая часть сложности исчезнет.

Если коллекция является полустатической (то есть вставки редки или, по крайней мере, имеют малый объем), то, безусловно, стоит рассмотреть простую в реализации статическую структуру данных (sdarray от Sadakane - хороший выбор) в сочетании с обновлением кэш. По сути, вы записываете обновления в традиционной структуре данных (например, B-дерево, три, хэш-таблица) и периодически массово обновляете «основную» структуру данных. Это очень популярный метод поиска информации, поскольку инвертированные индексы имеют много преимуществ для поиска, но их сложно обновить на месте. Если это так, пожалуйста, дайте мне знать в комментарии, и я исправлю этот ответ, чтобы дать вам несколько советов.

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

Таким образом, основной теоретический информационный результат заключается в том, что если вы храните элементов из юниверса из u элементов, а другой информации нет (например, нет корреляции между элементами), то вам нужно биты для его хранения. (Все логарифмы являются основанием-2, если не указано иное.) Вам нужно столько битов. Обойти это невозможно.nulog(un)+O(1)

Теперь немного терминологии:

  • Если у вас есть структура данных, которая может хранить данные и поддерживать ваши операции в битах пространства, мы называем это неявной структурой данных.log(un)+O(1)
  • Если у вас есть структура данных, которая может хранить данные и поддерживать ваши операции в битов пространства, мы называем это компактной структурой данных. Обратите внимание, что на практике это означает, что относительные накладные расходы (относительно теоретического минимума) находятся в пределах константы. Это может быть 5%, или 10%, или 10 раз.log(un)+O(log(un))=(1+O(1))log(un)
  • Если у вас есть структура данных, которая может хранить данные и поддерживать ваши операции в битов пространства, мы называем это сжатой структурой данных.log(un)+o(log(un))=(1+o(1))log(un)

Разница между лаконичным и компактным - это разница между маленьким и большим. Игнорирование абсолютной стоимости на мгновение ...

  • c n 0 n > n 0 g ( n ) < c f ( n )g(n)=O(f(n)) означает , что существует константа и число такой , что для всех , .cn0n>n0g(n)<cf(n)
  • c n 0 n > n 0 g ( n ) < c f ( n )g(n)=o(f(n)) означает , что для всех констант существует число , что для всех , .cn0n>n0g(n)<cf(n)

Неофициально, big-oh и little-oh оба находятся «в пределах постоянного фактора», но с big-oh постоянная выбирается для вас (разработчиком алгоритма, производителем ЦП, законами физики и т. Д.), Но с небольшим - ты сам выбираешь константу, и она может быть такой маленькой, как тебе нравится . Иными словами, при сжатых структурах данных относительные издержки становятся сколь угодно малыми по мере увеличения размера проблемы.

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

Хорошо, с этим под нашими поясами, давайте поставим некоторые цифры по проблеме. Предположим, что ключи являются битными целыми числами (поэтому размер юниверса равен ), и мы хотим хранить этих целых чисел. Давайте предположим, что мы можем волшебным образом организовать идеализированную хеш-таблицу с полным заполнением и без потерь, так что нам нужно ровно слотов для хеша.2 n 2 м 2 мn2n2m2m

Операция поиска будет хэшировать битный ключ, маскировать бит, чтобы найти слоты хэша, а затем проверять, соответствует ли значение в таблице ключу. Все идет нормально.мnm

Такая хеш-таблица использует битов. Можем ли мы сделать лучше, чем это?n2m

Предположим, что хеш-функция обратима. Тогда нам не нужно хранить весь ключ в каждом слоте хэша. Расположение слота хэша дает вам битов значения хэша, поэтому, если вы сохранили только оставшиеся биты , вы можете восстановить ключ из этих двух фрагментов информации (местоположение слота хэша и сохраненное там значение). Таким образом, вам потребуется бит памяти.м н - м ( н - м ) 2 мhmnm(nm)2m

Если мало по сравнению с , приближение Стирлинга и небольшая арифметика (доказательство - упражнение!) Показывают, что:2 н2m2n

(nm)2m=log(2n2m)+o(log(2n2m))

Таким образом, эта структура данных является краткой.

Однако есть два улова.

Первый улов - построение «хороших» обратимых хеш-функций. К счастью, это намного проще, чем кажется; криптографы постоянно делают обратимые функции, только они называют их «шифрами». Например, вы можете создать хеш-функцию в сети Фейстеля, которая является простым способом построения обратимых хеш-функций из необратимых хеш-функций.

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

Хеширование с кукушкой требует нескольких хеш-функций и требует, чтобы значения в слотах хешировались с тем, какая хеш-функция использовалась. Так, например, если вы используете четыре хеш-функции, вам нужно хранить дополнительные два бита в каждом слоте хеш-функции. Это все еще лаконично с ростом , так что на практике это не проблема, и все же лучше, чем хранить целые ключи.m

О, вы также можете посмотреть на деревья Ван Эмде Боаса.

БОЛЬШЕ МЫСЛИ

Если где-то около , то приблизительно равно , поэтому (еще раз), если предположить, что между значениями нет дальнейшей корреляции, вы в принципе не можете ничего сделать лучше, чем немного вектора. Вы заметите, что решение для хеширования, приведенное выше, действительно вырождается в этом случае (в итоге вы сохраняете один бит на слот хеша), но дешевле использовать ключ в качестве адреса, а не хеш-функцию.тыn журнала ( уu2 тыlog(un)u

Если очень близко к , вся литература по кратким структурам данных советует вам перевернуть смысл словаря. Сохраните значения, которые не встречаются в наборе. Однако теперь вы фактически должны поддерживать операцию удаления, и для поддержания краткого поведения вам также необходимо иметь возможность уменьшить структуру данных по мере того, как «добавляются» дополнительные элементы. Расширение хеш-таблицы является хорошо понятной операцией, но сокращение - нет.тыnu

Псевдоним
источник
Привет, что касается второго абзаца вашего ответа - я ожидаю, что каждый вызов insertбудет сопровождаться вызовом findс тем же аргументом. Так что, если findвозвращается true, то мы просто пропускаем insert. Таким образом, частота findзвонков больше, чем частота insertзвонков, даже когда nстановится близко к u, то insertзвонки становятся очень редкими.
HEKTO
Но вы ожидаете приблизиться к в конце концов? нun
Псевдоним
В реальном мире n растет, пока не достигнет вас, однако мы не можем предсказать, произойдет это или нет. Структура данных должна хорошо работать для любогоn <= u
HEKTO
Правильно. Тогда будет справедливо сказать, что мы не знаем ни одной структуры данных, которая бы лаконична (в вышеприведенном смысле) и которая бы достигала этого во всем диапазоне . Я думаю, что вам понадобится разреженная структура данных, когда , затем переключитесь на плотную (например, битовый вектор), когда около , а затем разреженную структуру данных с инвертированным чувство , когда близко к . п<упуnun<un ну тебяu2nu
Псевдоним
5

Похоже, вы хотите краткую структуру данных для проблемы динамического членства .

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

Проблема с членством - именно то, что вы описываете в своем вопросе:

SnU={0,1,2,3,,u1}u

  • find(x)xS
  • insert(x)xS
  • delete(x)xS

Если findподдерживается только операция, то это проблема статического членства. Если один из них insertили deleteподдерживается, но не оба, это называется полудинамическим , и если все три операции поддерживаются, то это называется проблемой динамического членства.

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

В теореме 5.1 статьи « Членство в постоянном времени и почти минимальном пространстве» Бродник и Мунро дают следующий результат:

O(B)

B=log(un)

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

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

Джо
источник
1
В реферате Бродника и Мунро ничего не говорится о вкладышах. Но их результат - это то, что мы можем ожидать, верно? Если n = u/2, то необходимое пространство максимально.
HEKTO
@AlekseyYakovlev На самом деле динамический случай в реферате не упоминается, но в моем ответе приведена теорема, которая касается динамического случая (из раздела 5).
Джо