Враховуючи два списки (не обов’язково відсортовані), який найефективніший нерекурсивний алгоритм знаходить перетин цих списків?
Враховуючи два списки (не обов’язково відсортовані), який найефективніший нерекурсивний алгоритм знаходить перетин цих списків?
Відповіді:
Ви можете помістити всі елементи першого списку в хеш-набір. Потім повторіть другий і для кожного з його елементів перевірте хеш, щоб побачити, чи існує він у першому списку. Якщо так, виведіть його як елемент перетину.
Можливо, ви захочете поглянути на фільтри Bloom. Вони є бітовими векторами, які дають імовірнісну відповідь, чи є елемент елементом набору. Набір перетину може бути реалізований простою побітовою операцією І. Якщо у вас велика кількість нульових перетинів, фільтр Bloom може допомогти вам швидко їх усунути. Однак вам все одно доведеться вдатися до одного з інших згаданих тут алгоритмів для обчислення фактичного перетину. http://en.wikipedia.org/wiki/Bloom_filter
без хешування, я думаю, у вас є два варіанти:
O(n + m)
але не завжди можливо.
O(n lg n) * 2 + O(n) * 2
є таким самим, як O(n lg n)
.
Зі списку функцій eviews здається, що він підтримує складні об'єднання та об'єднання (якщо це "об'єднання", як у термінології БД, він обчислює перетин). Тепер перекопайте вашу документацію :-)
Крім того, eviews має власний форум користувачів - чому б не запитати там_
за допомогою набору 1 побудуйте двійкове дерево пошуку за допомогою O(log n)
та повторіть набір 2 та виконайте пошук за BST m X O(log n)
сумоюO(log n) + O(m)+O(log n) ==> O(log n)(m+1)
в 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;
}
Ось ще одне можливе рішення, яке я придумав, приймає O (nlogn) за часовою складністю і без зайвого зберігання. Ви можете перевірити це тут https://gist.github.com/4455373
Ось як це працює: припускаючи, що набори не містять жодного повторення, об’єднайте всі набори в один і відсортуйте його. Потім прокрутіть об'єднаний набір і на кожній ітерації створіть підмножину між поточним індексом i та i + n, де n - кількість наборів, доступних у всесвіті. Те, що ми шукаємо під час циклу, - це повторювана послідовність розміру n, що дорівнює кількості множин у Всесвіті.
Якщо ця підмножина в i дорівнює цій підмножині в n, це означає, що елемент у i повторюється n разів, що дорівнює загальній кількості наборів. І оскільки в жодному наборі немає повторень, це означає, що кожен із наборів містить це значення, тому ми додаємо його до перетину. Потім ми зміщуємо індекс на i + те, що залишається між ним і n, оскільки точно жоден з цих індексів не збирається утворювати повторювану послідовність.
Спочатку відсортуйте обидва списки за допомогою швидкого сортування: 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 є рекурсивним, як сказано в коментарях, але схоже, що існують нерекурсивні реалізації
Використання вказівників пропуску та інструкцій SSE може покращити ефективність перетину списку.
Якщо є підтримка наборів (як ви називаєте їх у заголовку) як вбудованих, як правило, існує метод перетину.
У будь-якому випадку, як хтось сказав, що ви можете зробити це легко (я не буду надсилати поштовий індекс, хтось це вже зробив), якщо у вас сортуються списки. Якщо ви не можете використовувати рекурсію, то проблем немає. Є реалізації без рекурсії швидкого сортування .
Я отримав кілька хороших відповідей з цього приводу, які ви можете застосувати. Я ще не маю можливості спробувати їх, але оскільки вони також охоплюють перехрестя, вони можуть вам виявитися корисними.
У 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;
}
З визначення нотації Біг-О:
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), скажімо, що два списки однакового розміру.