Я ищу реализацию заданного типа данных. То есть мы должны
- поддерживать динамическое подмножество (размера ) из юниверса размера с
- операции
insert(x)
(добавить элементx
в ) иfind(x)
(проверяет,x
является ли элемент членом ).
Я не забочусь о других операциях. Для ориентации в приложениях, с которыми я работаю, имеем .
Мне известны реализации, которые обеспечивают обе операции за время , поэтому я больше всего беспокоюсь о размере структуры данных. Я ожидаю миллиарды записей, но хочу избежать обмена как можно больше.
Я готов пожертвовать временем выполнения, если это необходимо. Амортизируемое время выполнения - это то, что я могу допустить; ожидаемое время выполнения или время выполнения в недопустимо.
У меня есть идея, что если можно представить как объединение диапазонов [xmin, xmax]
, то мы сможем сэкономить на размере хранилища с ценой некоторого снижения производительности. Также возможны некоторые другие шаблоны данных, например [0, 2, 4, 6]
.
Не могли бы вы указать мне на структуры данных, которые могут сделать что-то подобное?
n
размер набора S. Он может увеличиваться с каждымinsert
или может оставаться неизменным, если элементx
уже находится в наборе.Ответы:
Ответ Джо очень хорош и дает вам все важные ключевые слова.
Вы должны знать, что сжатые исследования структуры данных все еще находятся на ранней стадии, и многие результаты в значительной степени теоретические. Многие из предложенных структур данных довольно сложны для реализации, но большая часть сложности связана с тем, что вам необходимо поддерживать асимптотическую сложность как по размеру юниверса, так и по количеству хранимых элементов. Если один из них является относительно постоянным, то большая часть сложности исчезнет.
Если коллекция является полустатической (то есть вставки редки или, по крайней мере, имеют малый объем), то, безусловно, стоит рассмотреть простую в реализации статическую структуру данных (sdarray от Sadakane - хороший выбор) в сочетании с обновлением кэш. По сути, вы записываете обновления в традиционной структуре данных (например, B-дерево, три, хэш-таблица) и периодически массово обновляете «основную» структуру данных. Это очень популярный метод поиска информации, поскольку инвертированные индексы имеют много преимуществ для поиска, но их сложно обновить на месте. Если это так, пожалуйста, дайте мне знать в комментарии, и я исправлю этот ответ, чтобы дать вам несколько советов.
Если вставки встречаются чаще, я предлагаю сжатое перемешивание. Основная идея достаточно проста, чтобы объяснить здесь, поэтому я сделаю это.
Таким образом, основной теоретический информационный результат заключается в том, что если вы храните элементов из юниверса из u элементов, а другой информации нет (например, нет корреляции между элементами), то вам нужно биты для его хранения. (Все логарифмы являются основанием-2, если не указано иное.) Вам нужно столько битов. Обойти это невозможно.n u log(un)+O(1)
Теперь немного терминологии:
Разница между лаконичным и компактным - это разница между маленьким и большим. Игнорирование абсолютной стоимости на мгновение ...
Неофициально, big-oh и little-oh оба находятся «в пределах постоянного фактора», но с big-oh постоянная выбирается для вас (разработчиком алгоритма, производителем ЦП, законами физики и т. Д.), Но с небольшим - ты сам выбираешь константу, и она может быть такой маленькой, как тебе нравится . Иными словами, при сжатых структурах данных относительные издержки становятся сколь угодно малыми по мере увеличения размера проблемы.
Конечно, размер проблемы может быть огромным, чтобы понять относительные накладные расходы, которые вы хотите, но у вас не может быть всего.
Хорошо, с этим под нашими поясами, давайте поставим некоторые цифры по проблеме. Предположим, что ключи являются битными целыми числами (поэтому размер юниверса равен ), и мы хотим хранить этих целых чисел. Давайте предположим, что мы можем волшебным образом организовать идеализированную хеш-таблицу с полным заполнением и без потерь, так что нам нужно ровно слотов для хеша.2 n 2 м 2 мn 2n 2m 2m
Операция поиска будет хэшировать битный ключ, маскировать бит, чтобы найти слоты хэша, а затем проверять, соответствует ли значение в таблице ключу. Все идет нормально.мn m
Такая хеш-таблица использует битов. Можем ли мы сделать лучше, чем это?n2m
Предположим, что хеш-функция обратима. Тогда нам не нужно хранить весь ключ в каждом слоте хэша. Расположение слота хэша дает вам битов значения хэша, поэтому, если вы сохранили только оставшиеся биты , вы можете восстановить ключ из этих двух фрагментов информации (местоположение слота хэша и сохраненное там значение). Таким образом, вам потребуется бит памяти.м н - м ( н - м ) 2 мh m n−m (n−m)2m
Если мало по сравнению с , приближение Стирлинга и небольшая арифметика (доказательство - упражнение!) Показывают, что:2 н2m 2n
Таким образом, эта структура данных является краткой.
Однако есть два улова.
Первый улов - построение «хороших» обратимых хеш-функций. К счастью, это намного проще, чем кажется; криптографы постоянно делают обратимые функции, только они называют их «шифрами». Например, вы можете создать хеш-функцию в сети Фейстеля, которая является простым способом построения обратимых хеш-функций из необратимых хеш-функций.
Второй улов заключается в том, что настоящие хеш-таблицы не идеальны, благодаря парадоксу дня рождения. Таким образом, вы захотите использовать более сложный тип хеш-таблицы, который позволит вам приблизиться к полной загрузке без разлива. Хеширование кукушки идеально подходит для этого, так как позволяет вам сколь угодно приблизиться к идеалу в теории и довольно близко на практике.
Хеширование с кукушкой требует нескольких хеш-функций и требует, чтобы значения в слотах хешировались с тем, какая хеш-функция использовалась. Так, например, если вы используете четыре хеш-функции, вам нужно хранить дополнительные два бита в каждом слоте хеш-функции. Это все еще лаконично с ростом , так что на практике это не проблема, и все же лучше, чем хранить целые ключи.m
О, вы также можете посмотреть на деревья Ван Эмде Боаса.
БОЛЬШЕ МЫСЛИ
Если где-то около , то приблизительно равно , поэтому (еще раз), если предположить, что между значениями нет дальнейшей корреляции, вы в принципе не можете ничего сделать лучше, чем немного вектора. Вы заметите, что решение для хеширования, приведенное выше, действительно вырождается в этом случае (в итоге вы сохраняете один бит на слот хеша), но дешевле использовать ключ в качестве адреса, а не хеш-функцию.тыn журнала ( уu2 тыlog(un) u
Если очень близко к , вся литература по кратким структурам данных советует вам перевернуть смысл словаря. Сохраните значения, которые не встречаются в наборе. Однако теперь вы фактически должны поддерживать операцию удаления, и для поддержания краткого поведения вам также необходимо иметь возможность уменьшить структуру данных по мере того, как «добавляются» дополнительные элементы. Расширение хеш-таблицы является хорошо понятной операцией, но сокращение - нет.тыn u
источник
insert
будет сопровождаться вызовомfind
с тем же аргументом. Так что, еслиfind
возвращаетсяtrue
, то мы просто пропускаемinsert
. Таким образом, частотаfind
звонков больше, чем частотаinsert
звонков, даже когдаn
становится близко кu
, тоinsert
звонки становятся очень редкими.n <= u
Похоже, вы хотите краткую структуру данных для проблемы динамического членства .
Напомним, что краткая структура данных - это такая структура, для которой требования к пространству «близки» к теоретической информации нижней границы, но, в отличие от сжатой структуры данных, по-прежнему допускают эффективные запросы.
Проблема с членством - именно то, что вы описываете в своем вопросе:
find(x)
x
insert(x)
x
delete(x)
x
Если
find
поддерживается только операция, то это проблема статического членства. Если один из нихinsert
илиdelete
поддерживается, но не оба, это называется полудинамическим , и если все три операции поддерживаются, то это называется проблемой динамического членства.Технически, я думаю, вы просили только структуру данных для полудинамической проблемы членства, но я не знаю каких-либо структур данных, которые бы использовали это ограничение и соответствовали вашим другим требованиям. Тем не менее, у меня есть следующая ссылка:
В теореме 5.1 статьи « Членство в постоянном времени и почти минимальном пространстве» Бродник и Мунро дают следующий результат:
Основная идея заключается в том, что они рекурсивно разделяют вселенную на диапазоны тщательно подобранных размеров, так что это даже звучит так, будто техники могут соответствовать тем направлениям, о которых вы думаете.
Однако, если вы ищете что-то, что вы можете реализовать, я не знаю, будет ли это лучшим выбором для вас. Я только просмотрел статью, и попытка объяснить детали выходит за рамки этого ответа. Они параметризуют свое решение, используя разные стратегии в зависимости от относительных размеров и . А динамическая версия структуры данных только набросок на бумаге.нu n
источник
n = u/2
, то необходимое пространство максимально.