Різниця обчислювальної множини між двома великими множинами


14

У мене є два великих наборів цілих чисел A і B . Кожен набір містить близько мільйона записів, і кожен запис - це натуральне число, яке становить не більше 10 цифр.

Який найкращий алгоритм для обчислення A∖B і B∖A ? Іншими словами, як я можу ефективно обчислити список записів A , які не є у B і навпаки? Яка найкраща структура даних для представлення цих двох наборів, щоб зробити ці операції ефективними?

Найкращий підхід, який я можу придумати, - це зберігання цих двох наборів у вигляді відсортованих списків та порівняння кожного елемента A проти кожного елемента B , лінійним способом. Чи можемо ми зробити краще?


Якщо ви готові зберігати його по-різному, можливо, ви зможете отримати кращі результати.
— Realz Slaw

Крім того, якщо ви готові отримати результати у вигляді неявної структури даних; ви можете просто створити таку структуру, яка запитує два набори, щоб відповісти на кожен власний запит.
— Realz Slaw

1
@ user917279 Одним із важливих моментів є: зазвичай ви можете компромісувати попередню обробку / час побудови, час запиту та використання пам'яті один проти одного. Ви редагуєте structe рідко, але запитуєте багато? Навпаки? Пам'ять хвилює чи ні? На такі запитання можна відповісти з практичної точки зору та інформувати про вибір "правильного" "теоретичного" конструкту.
— Рафаель

1
@Raphael Ви вважаєте, що можна зробити краще, ніж незрозумілі стійкі набори (за складністю), використовуючи більше пам’яті та / або витрачаючи більше часу на підготовку. Мені просто цікаво, якщо ви думаєте, що це можливо. Я не бачу таблиці пошуку як варіант для наборів вводу такого розміру.
— smossen

1
@ user917279 Якщо ви розглядаєте приклад двох величезних наборів, які однакові, то будь-яка структура даних, створена за допомогою хеш-консистенції, підтримує тестування рівності в O (1), оскільки однакові структури будуть об'єднані при створенні і таким чином матимуть однакове місце пам'яті. Збіжно стійкі набори використовують переваги хеш-консистенції також тоді, коли дві структури майже рівні. Складність - найкраща, яку я бачив поки що для замовлених наборів.
— smossen

Відповіді:


9

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

Нехай I=O(min(|A|,|B|,|AΔB|))

Тоді ви можете виконати операції і A Δ B , кожна з яких O ( I ⋅ log | A | + | B |A∪B,A∩B,A∖BAΔBочікуваний час. Отже, по суті, ви отримуєте мінімальний розмір двох наборів, або - розмір симетричної різниці, залежно від того, що менше. Це краще, ніж лінійне, якщо симетрична різниця невелика; тобто. якщо вони мають велике перехрестя. Насправді для двох операцій з різницею множин, які ви хочете, це практично чутливо до виходу, оскільки разом вони складають розмір симетричної різниці.O(I⋅log⁡|A|+|B|I)

Додаткову інформацію див. У розділі Налаштовані стійкі набори та карти від Olle Liljenzin (2013).


Обриви у статті впорядковані пошукові дерева. Я б не зараховував їх до несортивних структур даних.
— smossen

@smossen досить правдивий, я це відредагував.
— Realz Slaw

6

Лінійне сканування - це найкраще, що я знаю, як зробити, якщо набори представлені у вигляді відсортованих пов'язаних списків. Час роботи - .O(|A|+|B|)

Зауважте, що вам не потрібно порівнювати кожен елемент проти кожного елемента B , попарно. Це призвело б до часу виконання O ( |AB , що значно гірше. Натомість для обчислення симетричної різниці цих двох множин можна використовувати техніку, аналогічну операції "злиття" в об'єднанні, відповідним чином модифіковану для опущення значень, спільних для обох наборів.O(|A|×|B|)

Більш детально, ви можете побудувати рекурсивний алгоритм, як описано нижче для обчислення A∖B , припускаючи, що і B представлені як пов'язані списки зі своїми значеннями у відсортованому порядку:AB

difference(A, B):
    if len(B)=0:
        return A # return the leftover list
    if len(A)=0:
        return B # return the leftover list
    if A[0] < B[0]:
        return [A[0]] + difference(A[1:], B)
    elsif A[0] = B[0]:
        return difference(A[1:], B[1:])  # omit the common element
    else:
        return [B[0]] + difference(A, B[1:])

Я представляв це в псевдо-Python. Якщо ви не читаєте Python, він A[0]є головою пов'язаного списку A, A[1:]є рештою списку і +являє собою конкатенацію списків. З міркувань ефективності, якщо ви працюєте в Python, ви, мабуть, не хотіли б реалізувати його саме так, як вище - наприклад, може бути краще використовувати генератори, щоб уникнути створення багатьох тимчасових списків - але я хотів покажіть ідеї у найпростішій можливій формі. Мета цього псевдокоду - просто проілюструвати алгоритм, а не запропонувати конкретну реалізацію.

Я не думаю, що зробити це краще, якщо ваші набори представлені у вигляді відсортованих списків і ви хочете, щоб результат був наданий у вигляді відсортованого списку. Ви принципово повинні дивитися на кожен елемент і B . Неформальний ескіз обгрунтування: Якщо є якийсь елемент, який ви не переглянули, ви не можете його вивести, тому єдиний випадок, коли ви можете опустити погляд на елемент, це якщо ви знаєте, що він присутній в обохAB і в В , але як ви могли знати, що він присутній, якщо ви не подивилися на його значення?AB


фантастично, чи є у нас інші варіанти, якщо обмеження, що набори потрібно зберігати як упорядковані списки, буде знято?
— user917279

2

Якщо A і B мають однаковий розмір, роз'єднані та переплетені (наприклад, непарні числа в A і парні числа в B), то порівняння парних елементів у лінійний час, ймовірно, є оптимальним.

Якщо A і B містять блоки елементів, які знаходяться точно в одному з A або B, або в обох з них, можна обчислити різницю множин, об'єднання та перетину в сублінійний час. Наприклад, якщо A і B різняться в точно одному елементі, різницю можна обчислити в O (log n).

http://arxiv.org/abs/1301.3388


1
Він каже, що набори впорядковані, що може означати, що вони зберігаються як списки, дерева пошуку чи щось інше. Якщо дані потрібно зберігати у вигляді списків, досить нецікаво просити "найкращий алгоритм для обчислення AB", коли жоден алгоритм не міг би зробити краще, ніж сканування списків у лінійний час (для якого він уже знайшов алгоритм).
— smossen

1
Боже, ви зв'язали той самий папір, що і я (я, такий же, як і ви, швидше) ... назвіть свої посилання наступного разу: D
— Realz Slaw

@smossen фантастично, наскільки б я не знав (?), я представляв їх як упорядковані списки, але покірно вітаю й інші пропозиції.
— user917279

2

один із варіантів полягає у використанні бітвекторів для представлення наборів (де -та позиція представляє наявність або відсутність елемента), а операції типу набору потім зводяться до двійкових операцій, які можна швидко виконувати (і на декількох бітах паралельно) на цифрових комп'ютерах . в цьому випадку A - B = a ∧ ¯ b, де a , b - бітрейктори. відносна ефективність цієї техніки порівняно з іншими методами також залежить від розрідженості. для більш щільних наборів він може бути ефективнішим, ніж інші підходи. також, звичайно, вся операція бентежно паралельна, тому встановлені операції можна робити паралельно.nA−Ba∧b¯a,b


1010

1
Р., пропускає пункт. одномісний longможе зберігати 32 елементи або 1 byte, 8 елементів. тож записи 1М можна зберігати лише у ~ 125 КБ оперативної пам’яті! сховище може бути значно ефективнішим, ніж інші представлення, залежно від того, як реалізується проблема ...
— vzn

Тож вам знадобиться понад 12 Мб для наборів, якими цікавиться ОП. Це вибухає всі кеші (на даний момент) і буде жахливо для рідких наборів. Зокрема, створення порожнього набору домінує над усіма іншими операціями (для розріджених наборів). Кнут, до речі, вирішує це питання в TAoCP.
— Рафаель

12 МБ? так? Плакат сказав, що у нього є лише 2 набори. плакат не вказав рідкість / щільність його набору. на це вказується у моїй відповіді. Ви припускаєте, що у нього рідкісні набори? Немає одного правильного відповіді, підхід вказаний як альтернативний варіант, який може бути корисним залежно від обставин. це не рідко використовується в цьому контексті ...
— vzn

10101061010b≈1.15GB
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.