Ответ Джо очень хорош и дает вам все важные ключевые слова.
Вы должны знать, что сжатые исследования структуры данных все еще находятся на ранней стадии, и многие результаты в значительной степени теоретические. Многие из предложенных структур данных довольно сложны для реализации, но большая часть сложности связана с тем, что вам необходимо поддерживать асимптотическую сложность как по размеру юниверса, так и по количеству хранимых элементов. Если один из них является относительно постоянным, то большая часть сложности исчезнет.
Если коллекция является полустатической (то есть вставки редки или, по крайней мере, имеют малый объем), то, безусловно, стоит рассмотреть простую в реализации статическую структуру данных (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)<c⋅f(n)
- c n 0 n > n 0 g ( n ) < c ⋅ f ( n )g(n)=o(f(n)) означает , что для всех констант существует число , что для всех , .cn0n>n0g(n)<c⋅f(n)
Неофициально, big-oh и little-oh оба находятся «в пределах постоянного фактора», но с big-oh постоянная выбирается для вас (разработчиком алгоритма, производителем ЦП, законами физики и т. Д.), Но с небольшим - ты сам выбираешь константу, и она может быть такой маленькой, как тебе нравится . Иными словами, при сжатых структурах данных относительные издержки становятся сколь угодно малыми по мере увеличения размера проблемы.
Конечно, размер проблемы может быть огромным, чтобы понять относительные накладные расходы, которые вы хотите, но у вас не может быть всего.
Хорошо, с этим под нашими поясами, давайте поставим некоторые цифры по проблеме. Предположим, что ключи являются битными целыми числами (поэтому размер юниверса равен ), и мы хотим хранить этих целых чисел. Давайте предположим, что мы можем волшебным образом организовать идеализированную хеш-таблицу с полным заполнением и без потерь, так что нам нужно ровно слотов для хеша.2 n 2 м 2 мn2n2m2m
Операция поиска будет хэшировать битный ключ, маскировать бит, чтобы найти слоты хэша, а затем проверять, соответствует ли значение в таблице ключу. Все идет нормально.мnm
Такая хеш-таблица использует битов. Можем ли мы сделать лучше, чем это?n2m
Предположим, что хеш-функция обратима. Тогда нам не нужно хранить весь ключ в каждом слоте хэша. Расположение слота хэша дает вам битов значения хэша, поэтому, если вы сохранили только оставшиеся биты , вы можете восстановить ключ из этих двух фрагментов информации (местоположение слота хэша и сохраненное там значение). Таким образом, вам потребуется бит памяти.м н - м ( н - м ) 2 мhmn−m(n−m)2m
Если мало по сравнению с , приближение Стирлинга и небольшая арифметика (доказательство - упражнение!) Показывают, что:2 н2m2n
(n−m)2m=log(2n2m)+o(log(2n2m))
Таким образом, эта структура данных является краткой.
Однако есть два улова.
Первый улов - построение «хороших» обратимых хеш-функций. К счастью, это намного проще, чем кажется; криптографы постоянно делают обратимые функции, только они называют их «шифрами». Например, вы можете создать хеш-функцию в сети Фейстеля, которая является простым способом построения обратимых хеш-функций из необратимых хеш-функций.
Второй улов заключается в том, что настоящие хеш-таблицы не идеальны, благодаря парадоксу дня рождения. Таким образом, вы захотите использовать более сложный тип хеш-таблицы, который позволит вам приблизиться к полной загрузке без разлива. Хеширование кукушки идеально подходит для этого, так как позволяет вам сколь угодно приблизиться к идеалу в теории и довольно близко на практике.
Хеширование с кукушкой требует нескольких хеш-функций и требует, чтобы значения в слотах хешировались с тем, какая хеш-функция использовалась. Так, например, если вы используете четыре хеш-функции, вам нужно хранить дополнительные два бита в каждом слоте хеш-функции. Это все еще лаконично с ростом , так что на практике это не проблема, и все же лучше, чем хранить целые ключи.m
О, вы также можете посмотреть на деревья Ван Эмде Боаса.
БОЛЬШЕ МЫСЛИ
Если где-то около , то приблизительно равно , поэтому (еще раз), если предположить, что между значениями нет дальнейшей корреляции, вы в принципе не можете ничего сделать лучше, чем немного вектора. Вы заметите, что решение для хеширования, приведенное выше, действительно вырождается в этом случае (в итоге вы сохраняете один бит на слот хеша), но дешевле использовать ключ в качестве адреса, а не хеш-функцию.тыn журнала ( уu2 тыlog(un)u
Если очень близко к , вся литература по кратким структурам данных советует вам перевернуть смысл словаря. Сохраните значения, которые не встречаются в наборе. Однако теперь вы фактически должны поддерживать операцию удаления, и для поддержания краткого поведения вам также необходимо иметь возможность уменьшить структуру данных по мере того, как «добавляются» дополнительные элементы. Расширение хеш-таблицы является хорошо понятной операцией, но сокращение - нет.тыnu