Ефективне видалення дублікатів та з низькою витратою пам’яті


9

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

Один із способів цього можна побачити:

  • ми маємо діапазон цілих чисел S={1,,N} з N великий (скажімо 240)
  • у нас є функція f:SS нібито з багатьма зіткненнями (зображення рівномірно розподілені в S)
  • тоді нам потрібно зберігати f[S], це є {f(x)|xS}

Я маю досить точну (імовірнісну) оцінку того, що |f[S]| є, і тому може виділяти структури даних заздалегідь (скажімо |f[S]|230).

У мене було кілька ідей, але я не впевнений, що було б найкращим підходом:

  • про біт не виникає сумніву, оскільки вхідний набір не вписується в пам'ять.
  • хеш-таблиця, але (1) їй потрібна деяка об'єм пам'яті, скажімо, 150% |f[S]| та (2) таблицю слід досліджувати, коли її будують, що вимагає додаткового часу через об'єм пам'яті.
  • сорт "на льоту", бажано з O(N)складність (сортування порівняння). З цього приводу я не впевнений, яка головна різниця між сортуванням відра і спалахом .
  • простий масив з двійковим деревом пошуку, але для цього потрібно O(Nlog|f[S]|) час.
  • можливо, використання фільтрів Bloom або подібної структури даних може бути корисним для розслаблення (з помилковими позитивами) проблеми.

Деякі питання щодо stackoverflow, схоже, вирішують подібні речі ( /programming/12240997/sorting-array-in-on-run-time , /programming/3951547/java -array-find-дублікати ), але, схоже, жоден не відповідає моїм вимогам.


2
Чи потрібно перераховувати f [S] (що б це не було), чи мати можливість швидко сказати, чи є в ньому якийсь x?
Жил "ТАК - перестань бути злим"

@Gilles: Я вважаю, що, оскільки жодної очевидної структури в f [S] неможливо знайти, два рішення є рівнозначними.
doc

Ваші номери не складаються. Очікуване зображення випадкової функції в області розміруN приблизно (11/e)N. Інше питання полягає в тому, що це відбувається через256буде тривати занадто багато часу, якщо у вас не буде суперкомп'ютер чи великий кластер.
Yuval Filmus

1
Час для двійкового дерева пошуку був би O(Nlog|f[S]|), яка може бути або не бути близькою O(NlogN)на практиці, але все ж є більш точним.
jmad

1
З N256, чи не буде алгоритм лінійного часу також надмірним? (З моїх розрахунків, навіть якщо врахувати один елементSза 1 наносекунд, це знадобило б вам хороші 2 роки!).
Ар'ябхата

Відповіді:


1

Чому б не бен і ланцюжок?

Ідея полягає в тому, щоб зберігати додатні цілі числа, що можуть бути представлені n=k+m біти в масиві A з 2k записи, що представляють діапазони значень: запис A[y], y0, представляє діапазон [2my,2m(y+1)1]. Для будь-якого1x<2n ми можемо написати x=2my+z де y має k біт і z має mбіт. Спробуйте зберігатиz (не x!) за місцезнаходженням y:

  • Коли A[y]=z вже нічого не робіть: x є дублікатом.

  • Коли A[y] неініціалізований, зберігати z у A[y].

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

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

Коли розподіл близький до рівномірного і 2k перевищує N, ланцюг не буде сильним (це можна оцінити звичайними способами), і ланцюги, як правило, будуть короткими. Коли розподіл є неоднорідним, алгоритм все ще працює, але може досягти квадратичного моменту часу. Якщо така можливість є, використовуйте щось більш ефективне, ніж ланцюги (і заплатите трохи накладні витрати на зберігання).

Необхідне місце для зберігання - не більше 2n біт для A і 22k біти для ланцюгів (припускаючи mk). Це саме простір, необхідний для зберігання2k значення nбіт кожен. Якщо ви впевнені в рівномірності, ви можете виділити сховище для ланцюгів. Якщо неоднорідність є можливою, можливо, ви захочете збільшитиk і повністю відстоюють ланцюгове зберігання.

Альтернативний спосіб думати про це рішення є те , що вона є хеш - таблиця з особливо хорошою хеш - функції (взятиk найбільш значущі біти), і через це нам потрібно зберігати лише найменші значущі m=nk біти в таблиці.

Існують способи перекриття пам’яті для ланцюгів із накопичувачем для A але це, здається, не варто турбувати, тому що це не заощадить багато (припустимо m набагато менше, ніж k) простір і ускладнить розробку, налагодження та підтримку коду.


1
Я думаю, що другий-останній абзац є головним тут, і, ймовірно, повинен бути вгорі (як ідея). Я не знаю терміна "смітник і ланцюжок" (хоча це має сенс після прочитання поста). Цю ідею можна поширити на спроби .
Рафаель

Отже, це так Θ(n2)на погано розподілені входи. Я не бачу, наскільки це ефективно.
einpoklum

@einpoklum Ця відповідь чітко описує умови, в яких рішення є ефективним.
бер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.