Ефективний алгоритм перетину списку


76

Враховуючи два списки (не обов’язково відсортовані), який найефективніший нерекурсивний алгоритм знаходить перетин цих списків?


3
Це звучить як запитання домашнього завдання - чи не так?
Ерік Форбс,

29
Насправді ні. Я працюю, і мені доводиться програмувати в середовищі статистичного моделювання, яке називається eviews. Eviews не має вбудованого перетину, а також не підтримує рекурсію. Мені потрібен швидкий алгоритм, оскільки мої набори, як правило, великі, і програму потрібно часто запускати. Дякую!

4
Чи валорії в кожному списку унікальні? Якщо так, ви можете приєднатись до списків, відсортувати результат та шукати дублікати.
Фабіо Чеконелло

1
Скільки елементів у наборах зазвичай? (наприклад, чи варто витратити свій час, щоб спробувати впровадити хеш, або ви можете впоратися з сортуванням = O (n log n)?)
Jason S

2
Який тип даних ви сортуєте? Іноді є характеристика даних, якою ви можете скористатися при розробці алгоритму.
AShelly

Відповіді:


42

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


Це звучить добре, але я не вірю, що я також маю доступ до алгоритмів хешування. Будь-які пропозиції?

5
Тоді, можливо: * list list1 (time: n log n) * sort list2 (time: n log n) * об'єднати два і перевірити наявність подібних записів під час ітерації двох відсортованих списків одночасно (лінійний час)
Френк

2
У мене недостатньо точок, щоб коментувати інші потоки, але щодо того, що швидке сортування є рекурсивним: ви можете реалізувати це без рекурсії. Дивіться тут, наприклад: codeguru.com/forum/archive/index.php/t-333288.html
Френк

4
Якщо у вас є доступ до масивів, ви, безсумнівно, можете створити власну хеш-таблицю. Побудова розумної хеш-функції зазвичай досить проста.
Кіт Ірвін

1
І як це зробити для кількох списків? Скажімо, у вас є декілька списків, і ви хочете перетин усіх? На моєму розумінні, це все одно піде таким чином: створити хеш для першого, почати ітерацію над рештою списків і перевірити, чи кожен з їх елементів існує в хеші .. правильно?
хан

24

Можливо, ви захочете поглянути на фільтри Bloom. Вони є бітовими векторами, які дають імовірнісну відповідь, чи є елемент елементом набору. Набір перетину може бути реалізований простою побітовою операцією І. Якщо у вас велика кількість нульових перетинів, фільтр Bloom може допомогти вам швидко їх усунути. Однак вам все одно доведеться вдатися до одного з інших згаданих тут алгоритмів для обчислення фактичного перетину. http://en.wikipedia.org/wiki/Bloom_filter


Це захоплюючий підхід для ефективного визначення того, чи перекриваються два великі набори.
Rick Sladkey

9

без хешування, я думаю, у вас є два варіанти:

  • Наївним способом буде порівняння кожного елемента з кожним іншим елементом. O (n ^ 2)
  • Іншим способом було б спочатку сортувати списки, а потім перебирати їх: O (n lg n) * 2 + 2 * O (n)

І ще одне: якщо можливо додати властивість до кожного елемента, спершу скиньте його до нуля для всіх елементів обох наборів, потім встановіть його на 1 в одному з наборів, а потім скануйте через другий набір знаходження елементів із набором властивостей до 1. Це, O(n + m)але не завжди можливо.
Роман Старков

Можливо, його можна покращити за допомогою двійкового пошуку O (log n)?
лицар

6
Просто примітка, що O(n lg n) * 2 + O(n) * 2є таким самим, як O(n lg n).
porglezomp

Перш за все сортування пов'язаного списку не є nlogn, оскільки у вас немає доступу O (1), вам потрібно перемістити його в масив. По-друге, вам потрібно відсортувати лише один список, а потім виконати двійковий пошук по ньому з кожним елементом першого списку.
shinzou

7

Зі списку функцій eviews здається, що він підтримує складні об'єднання та об'єднання (якщо це "об'єднання", як у термінології БД, він обчислює перетин). Тепер перекопайте вашу документацію :-)

Крім того, eviews має власний форум користувачів - чому б не запитати там_


6

за допомогою набору 1 побудуйте двійкове дерево пошуку за допомогою O(log n)та повторіть набір 2 та виконайте пошук за BST m X O(log n)сумоюO(log n) + O(m)+O(log n) ==> O(log n)(m+1)


2
Для частини бінарного дерева пошуку все ще потрібно відсортувати один зі списків (що додасть O (m log m) або O (n log n) до складності). Однак це все ще дуже корисна відповідь: у моєму випадку у мене є два списки, що містять однакові об’єкти, але кожен сортується за різними атрибутами об’єктів - і мені потрібно дізнатись, які об’єкти є в обох списках. Ця відповідь є агностичною щодо атрибута, за яким сортується кожен список. Дякую!
accidental_PhD

2
Насправді, побудова дерева - це O (n log n), тож загалом O ((n + m) log n)
c-urchin

6

в C ++ можна спробувати наступне, використовуючи карту STL

vector<int> set_intersection(vector<int> s1, vector<int> s2){

    vector<int> ret;
    map<int, bool> store;
    for(int i=0; i < s1.size(); i++){

        store[s1[i]] = true;
    }
    for(int i=0; i < s2.size(); i++){

        if(store[s2[i]] == true) ret.push_back(s2[i]);

    }
    return ret;
}

3

Ось ще одне можливе рішення, яке я придумав, приймає O (nlogn) за часовою складністю і без зайвого зберігання. Ви можете перевірити це тут https://gist.github.com/4455373

Ось як це працює: припускаючи, що набори не містять жодного повторення, об’єднайте всі набори в один і відсортуйте його. Потім прокрутіть об'єднаний набір і на кожній ітерації створіть підмножину між поточним індексом i та i + n, де n - кількість наборів, доступних у всесвіті. Те, що ми шукаємо під час циклу, - це повторювана послідовність розміру n, що дорівнює кількості множин у Всесвіті.

Якщо ця підмножина в i дорівнює цій підмножині в n, це означає, що елемент у i повторюється n разів, що дорівнює загальній кількості наборів. І оскільки в жодному наборі немає повторень, це означає, що кожен із наборів містить це значення, тому ми додаємо його до перетину. Потім ми зміщуємо індекс на i + те, що залишається між ним і n, оскільки точно жоден з цих індексів не збирається утворювати повторювану послідовність.


Сортування зв'язаного списку не може бути nlogn
shinzou

2

Спочатку відсортуйте обидва списки за допомогою швидкого сортування: O (n * log (n). Потім порівняйте списки, переглянувши спочатку найнижчі значення, і додайте загальні значення. Наприклад, у lua):

function findIntersection(l1, l2)
    i, j = 1,1
    intersect = {}

    while i < #l1 and j < #l2 do
        if l1[i] == l2[i] then
            i, j = i + 1, j + 1
            table.insert(intersect, l1[i])
        else if l1[i] > l2[j] then
            l1, l2 = l2, l1
            i, j = j, i
        else
            i = i + 1
        end
    end

    return intersect
end

який O(max(n, m))деn іm є розміри списків.

EDIT: quicksort є рекурсивним, як сказано в коментарях, але схоже, що існують нерекурсивні реалізації


Чи не є швидкий сорт рекурсивним? Або існує його нерекурсивна версія?

Я б не називав це O (max (n, m)). Ви теж робите два види.
Том Ріттер,

Чи існує нерекурсивна версія mergesort, яка також може працювати?

Хепсорт не є рекурсивним, але додає деякі потреби в структурі даних. Це було б нормально?
Вукай

1
Існує нерекурсивна швидка сортування. Натисніть повний інтервал для сортування на стек. Потім у циклі, спливаючи, потім розділяючи інтервал. Будь-які інтервали, які потребують подальшого сортування, висуваються на стек. Поверніться до початку циклу, витріть розділ ... Повторіть промивання, поки стопка не порожня.
EvilTeach


1

Чому б не реалізувати власну просту хеш-таблицю чи хеш-набір? Варто уникати перетину nlogn, якщо ваші списки великі, як ви кажете.

Оскільки ви трохи знаєте про свої дані заздалегідь, ви зможете вибрати хорошу хеш-функцію.


1

Я відстоюю ідею "наборів". У JavaScript ви можете використовувати перший список для заповнення об’єкта, використовуючи елементи списку як імена. Потім ви використовуєте елементи списку з другого списку і перевіряєте, чи існують ці властивості.


1

Якщо є підтримка наборів (як ви називаєте їх у заголовку) як вбудованих, як правило, існує метод перетину.

У будь-якому випадку, як хтось сказав, що ви можете зробити це легко (я не буду надсилати поштовий індекс, хтось це вже зробив), якщо у вас сортуються списки. Якщо ви не можете використовувати рекурсію, то проблем немає. Є реалізації без рекурсії швидкого сортування .


1. Якщо eviews підтримуватиме набори, він, мабуть, запропонує метод для перетину множин. 2. Як тут може допомогти об’єднання двох наборів. Перетином є ті елементи, які є в обох наборах. Коли я тут приєднуюсь, я думаю про обчислення об'єднання двох множин
f3lix

Java підтримує набори, але не має вбудованих функцій перетину.
lensovet

2
@lensovet: якщо він реалізує java.util.Set, існує метод java.util.Set.retainAll (Collection). У його документації написано: "Якщо вказана колекція також є набором, ця операція ефективно модифікує цей набір таким чином, що його значення є перетином двох наборів."
Андреа Амбу,

0

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


0

У PHP щось на зразок

function intersect($X) { // X is an array of arrays; returns intersection of all the arrays
  $counts = Array(); $result = Array();
  foreach ($X AS $x) {
    foreach ($x AS $y) { $counts[$y]++; }
  }
  foreach ($counts AS $x => $count) {
    if ($count == count($X)) { $result[] = $x; }
  }
  return $result;
}

1
Якщо у вас є дублікати в будь-якому з масивів, ви отримаєте неправильну поведінку.
Славек

0

З визначення нотації Біг-О:

T (N) = O (f (N)), якщо є позитивні константи c і n 0 такі, що T (N) ≤ cf (N), коли N ≥ n 0.

Що на практиці означає, що якщо два списки порівняно невеликі за розміром, скажімо, щось менше 100 елементів у кожному з двох для циклів працює чудово. Зациклюйте перший список і знайдіть подібний об’єкт у другому. У моєму випадку це чудово працює, тому що в моїх списках не буде більше 10 - 20 елементів. Однак хорошим рішенням є сортування першого O (n log n), сортування другого також O (n log n) і їх об'єднання, інший O (n log n), грубо вимовляючи O (3 n log n), скажімо, що два списки однакового розміру.

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