Эффективное удаление дубликатов с небольшим объемом памяти

9

Я хочу эффективно отфильтровать список целых чисел для дубликатов таким образом, чтобы хранить только полученный набор.

Один способ это можно увидеть:

  • у нас есть диапазон целых чисел с N большим (скажем, 2 40 )S={1,,N}N240
  • у нас есть функция с, предположительно, многими столкновениями (изображения равномерно распределены в S )f:SSS
  • тогда нам нужно хранить , то есть { f ( x ) | x S }f[S]{f(x)|xS}

У меня есть достаточно точная (вероятностная) оценка того, что есть, и поэтому может выделять структуры данных заранее (скажем, | f [ S ] |2 30 ).|f[S]||f[S]|230

У меня было несколько идей, но я не уверен, что будет лучшим подходом:

  • о наборе битов не может быть и речи, потому что входной набор не помещается в память.
  • хеш-таблицу, но (1) она требует некоторой перегрузки памяти, скажем, 150% от и (2) таблица должна быть исследована при сборке, что требует дополнительного времени из-за нехватки памяти.|f[S]|
  • сортировка «на лету», предпочтительно со сложностью (сортировка без сравнения). В связи с этим я не уверен, в чем заключается основное различие между сортировкой сегментов и флэш- сортировкой .O(N)
  • простой массив с бинарным деревом поиска, но для этого требуется время .O(Nlog|f[S]|)
  • возможно, использование фильтров Блума или подобной структуры данных может быть полезно для ослабления (с ложными срабатываниями) проблемы.

Некоторые вопросы по stackoverflow, кажется, решают такие вещи ( /programming/12240997/sorting-array-in-on-run-time , /programming/3951547/java -array-find-duplicates ), но ни один из них не соответствует моим требованиям.

доктор
источник
2
Вам нужно перечислить f [S] (что бы это ни было) или уметь быстро определить, есть ли в нем какой-то x?
Жиль "ТАК ... перестать быть злым"
@ Жиль: Я считаю, что, поскольку в f [S] не может быть найдено никакой очевидной структуры, оба решения эквивалентны.
док.
Ваши номера не складываются. Ожидается изображение случайной функции на области размером составляет примерно ( 1 - 1 / е ) Н . Другая проблема заключается в том, что проход через 2 56 займет слишком много времени, если в вашем распоряжении нет суперкомпьютера или большого кластера. N(11/e)N256
Юваль Фильмус
1
Время для двоичного дерева поиска будет , котороена практикеможет быть или не быть близко к O ( N log N ), но все же является более точным. O(Nlog|f[S]|)O(NlogN)
Джмад
1
С , не будет линейный алгоритм времени непомерно тоже? (По моим расчетам, даже если вы рассмотрите один элемент S за 1 наносекунду, это займет у вас хорошие 2 года!). N256S
Арьябхата

Ответы:

1

Почему не мусорное ведро и цепь?

Идея состоит в том, чтобы хранить натуральные числа, представимые битами, в массиве A из 2 kn=k+mA2k записей, представляющих диапазоны значений: запись , y 0 , представляет диапазон [ 2 m y , 2 m ( y) + 1 ) - 1 ] . Для любого 1 x < 2 n мы можем написать x = 2 m yA[y]y0[2my,2m(y+1)1]1x<2n где y имеет k битов, а z имеет m битов. Попробуйте сохранить z (не x !) В месте y :x=2my+zykzmzxy

  • Когда , ничего не делать: x является дубликатом.A[y]=zx

  • Когда не инициализирован, сохраните z в A [ y ] .A[y]zA[y]

  • В противном случае сохраните индекс в отдельном массиве, который используется для связывания (которые столкнулись в точке y ) в связанных списках. Вам нужно будет выполнить линейный поиск по списку, возглавляемому A [ y ], и, в зависимости от того, что обнаруживает поиск, потенциально вставить z в список.zyA[y]z

В конце концов, легко восстановить, просматривая инициализированные записи A и - путем простого объединения двух цепочек битов - повторной сборки каждого z, найденного в местоположении y (либо непосредственно, либо внутри цепочки, на которую есть ссылка), в исходную значение x = 2 m y + z .f(S)Azyx=2my+z

Когда распределение близко к равномерному и превышает N , цепочки не будет много (это можно оценить обычными способами), и цепочки будут иметь тенденцию быть короткими. Когда распределение неоднородно, алгоритм все еще работает, но может достигнуть квадратичной синхронизации. Если это возможно, используйте что-то более эффективное, чем цепочки (и заплатите немного за хранение).2kN

Необходимая память составляет не более битов для A и 2 2 k битов для цепочек (при условии, что m k ). Это именно то пространство, которое требуется для хранения 2 k значений по n битов каждое. Если вы уверены в единообразии, вы можете перераспределить хранилище для цепей. Если возможна неоднородность, вы можете увеличить k и полностью защитить цепочку хранения.2nA22kmk2knk

Альтернативный способ мышления об этом решении является то , что она является хэш - таблица с особенно хорошей хеш - функции (взять наиболее значимые биты) , и из - за этого, мы только должны хранить наименее значимый т = п - K битов стол.km=nk

Существуют способы наложения хранилища для цепей на хранилище для но, похоже, это не стоит беспокоиться, потому что это не сэкономит много (при условии, что m намного меньше, чем k ) пространства и усложнит разработку кода, отлаживать и поддерживать.Amk

whuber
источник
1
Я думаю, что второй-последний абзац здесь центральный, и, вероятно, должен быть наверху (как идея). Я не знаю термин «мусорное ведро» (хотя это имеет смысл после прочтения поста). Эта идея может быть распространена на попытки .
Рафаэль
Итак, это на плохо распределенных входах. Я не вижу, насколько это эффективно. Θ(n2)
einpoklum
@einpoklum Этот ответ явно описывает условия, в которых решение является эффективным.
whuber