Чому хтось використовуватиме набір замість unororder_set?


145

Представляємо C ++ 0x, unordered_setякий доступний у boostбагатьох інших місцях. Я розумію, що unordered_setце хеш-таблиця зі O(1)складністю пошуку. З іншого боку, setце не що інше, як дерево зі log(n)складністю пошуку. Чому б на землі хтось використовував setзамість цього unordered_set? тобто чи є вже потреба set?


22
У вашому питанні принципово задається питання, чи є вже потрібне дерево.
Вінко Врсалович

2
Я думаю, я чітко заявив це в першому рядку, що це якось дурне питання. Мені чогось не вистачало, і тепер я отримав відповідь :)
AraK

2
Справжня причина полягає в тому, що все не так, як здається. Між ними є багато сірого та іншого кольору. Вам потрібно пам’ятати, що ці контейнери - це інструменти. Іноді продуктивність не є вирішальною, і зручність набагато важливіша. Якщо б усі шукали найефективнішого рішення, ми "в першу чергу ніколи не використовували C ++ (не кажучи вже про Python) і постійно писали та оптимізували код машинною мовою"
AturSams

(Чому на землі хтось використовуватиме загальну назву для реалізації / інтерфейсу із обіцянками, що перевищують ті, які мають на увазі це ім'я, створюючи незручну ситуацію для тих, хто без них?)
сіра борода

Відповіді:


219

Коли для того, хто хоче повторити предмети набору, питання має значення.


Чи впорядковано це відповідно до порядку вставки чи за реальним порівнянням за допомогою операторів < >?
Щось щось щось

2
Упорядковано використовувати std :: менше за замовчуванням; Ви можете перекрити це та подати власного оператора порівняння. cplusplus.com/reference/set/set
самогонник

Або іноді, коли ви хочете лише повторити, навіть якщо замовлення не має значення.
mfnx

319

Непорядковані набори повинні оплачувати свій O (1) середній час доступу кількома способами:

  • setвикористовує менше пам'яті, ніж unordered_setдля зберігання однакової кількості елементів.
  • Для невеликої кількості елементів пошук у програмі setможе бути швидшим, ніж пошук у an unordered_set.
  • Хоча багато операцій виконуються швидше в середньому випадку для unordered_setїх часто гарантовано мати більш гірші складності регістра для set(наприклад insert).
  • Цей set сортування елементів корисно, якщо ви хочете отримати доступ до них по порядку.
  • Ви можете лексикографічно порівнювати різні setз з <, <=, >і >=. unordered_sets не потрібні для підтримки цих операцій.


9
+1, всі чудові бали. Люди схильні випускати з уваги той факт, що хештелі мають середній час доступу O (1) , тобто вони можуть періодично мати великі затримки. Відмінність може бути важливою для систем реального часу.
j_random_hacker

Хороші моменти, однак тут ( en.cppreference.com/w/cpp/container/unordered_set/operator_cmp ) зазначено, що ми можемо порівняти невпорядковані_набори.
Michiel uit het Broek

5
Визначте "невелику кількість елементів"
Sunjay Varma

4
@SunjayVarma, як правило, 100 елементів є хорошим відрізком між ними. Коли ви сумніваєтесь, ніщо не може замінити тестування продуктивності обох у вашому конкретному випадку використання.
Нейт

3
@MichieluithetBroek Зазначається лише порівняння рівності, а не впорядкування ( <).
lisyarus

26

Кожного разу, коли ви віддаєте перевагу дереву столу хешу.

Наприклад, хеш-таблиці є "O (n)" в гіршому випадку. O (1) - середній випадок. Дерева в гіршому випадку - "O ( log n)".


18
/ Збалансовані / дерева є O (ln n) в гіршому випадку. Ви можете одержати O (n) дерев (фактично пов'язані списки).
страгер

5
Якщо ви можете написати досить розумну хеш-функцію, ви можете майже завжди отримати O (1) perf з хеш-таблиці. Якщо ви не можете написати таку хеш-функцію, якщо вам потрібно перебрати "на порядок" над вашим набором, тоді ви повинні використовувати дерево. Але вам не слід використовувати дерево, тому що ви боїтеся "O (n) найгірших показників".
Джастін Л.

6
стаджер: Щоб бути педантичним, так. Однак ми говоримо про набір в C ++, який, як правило, реалізується як збалансоване дерево бінарного пошуку . Ми повинні вказати фактичну операцію, щоб говорити про складність. У цьому контексті очевидно, що ми говоримо про пошук.
Мехрдад Афшарі

1
Джастін Л: Це лише одна причина, що ти можеш віддати перевагу дереву. Ядром моєї відповіді є перший рядок. Кожен раз, коли ви віддаєте перевагу структурі даних дерев, а не хеш-таблиці. Існує маса випадків, коли дерева вважають за краще хеш-таблиць. Таблиці хешу особливо присмоктуються до таких речей, як "перехрестя діапазону".
Мехрдад Афшарі

2
stl дерева - це майже універсально реалізовані червоно-чорні дерева, вдосконалене дерево самоврівноваження. Дійсно є випадки, коли О (n) шукати в гіршому випадку неприйнятно. Веб-сервіс, який надає та інтерфейс для зберігання цінностей користувачів, не повинен використовувати хеш-карту, оскільки зловмисник може ефективно створювати DoS, зберігаючи спеціально створені значення. Критичні, залежні від часу системи, можливо, також не дозволяють шукати O (n), контроль повітряного руху тощо. Хоча, як правило, ви праві, використовуйте хеш-карти за замовчуванням і перемикайте версію дерева лише тоді, коли у вас є реальна потреба.
deft_code

14

Використовуйте встановити, коли:

  1. Нам потрібні впорядковані дані (окремі елементи).
  2. Нам доведеться роздрукувати / отримати доступ до даних (у відсортованому порядку).
  3. Нам потрібен попередник / наступник елементів.

Використовуйте не упорядкований_сет, коли:

  1. Нам потрібно зберегти набір чітких елементів і замовлення не потрібно.
  2. Нам потрібен одноелементний доступ, тобто відсутність проходу.

Приклади:

набір:

Вхід: 1, 8, 2, 5, 3, 9

Вихід: 1, 2, 3, 5, 8, 9

Не упорядкований_сет:

Вхід: 1, 8, 2, 5, 3, 9

Вихід: 9 3 1 8 2 5 (можливо, цей порядок, під впливом хеш-функції)

В основному різниця:

введіть тут опис зображення

Примітка: (в деяких випадках setзручніше), наприклад, використання vectorключа

set<vector<int>> s;
s.insert({1, 2});
s.insert({1, 3});
s.insert({1, 2});

for(const auto& vec:s)
    cout<<vec<<endl;   // I have override << for vector
// 1 2
// 1 3 

Причина, чому vector<int>може бути ключовою, setтому що vectorпереосмислити operator<.

Але якщо ви використовуєте, unordered_set<vector<int>>ви повинні створити хеш-функцію для vector<int>, тому що вектор не має хеш-функції, тож вам слід визначити такий, як:

struct VectorHash {
    size_t operator()(const std::vector<int>& v) const {
        std::hash<int> hasher;
        size_t seed = 0;
        for (int i : v) {
            seed ^= hasher(i) + 0x9e3779b9 + (seed<<6) + (seed>>2);
        }
        return seed;
    }
};

vector<vector<int>> two(){
    //unordered_set<vector<int>> s; // error vector<int> doesn't  have hash function
    unordered_set<vector<int>, VectorHash> s;
    s.insert({1, 2});
    s.insert({1, 3});
    s.insert({1, 2});

    for(const auto& vec:s)
        cout<<vec<<endl;
    // 1 2
    // 1 3
}

ви можете бачити, що в деяких випадках unordered_setскладніше.

В основному цитується з: https://www.geeksforgeeks.org/set-vs-unordered_set-c-stl/ https://stackoverflow.com/a/29855973/6329006


6

Оскільки std :: set є частиною Standard C ++, а unororder_set - не. C ++ 0x НЕ є стандартом, і це не Boost. Для багатьох із нас мобільність є важливою, і це означає дотримання стандарту.


2
Якщо я правильно його розумію, він не запитує, чому люди все ще використовують набір. Він інформує про C ++ 0x.
Йоханнес Шауб - ліб

2
Може бути. Я думав, що всі знають хеш-таблиці та дерева вирішують різні проблеми.

21
Ну, це стандарт зараз (лише зайняло кілька років)
Клейтон Х'юз

6

Розглянемо алгоритми проходження лінії. Ці алгоритми не зможуть повністю зійти з хеш-таблицями, але прекрасно працюють із збалансованими деревами. Щоб навести конкретний приклад алгоритму швидкої лінії, розглянемо алгоритм фортуни. http://en.wikipedia.org/wiki/Fortune%27s_algorithm


1
Я думаю, що таке посилання є надто складним, враховуючи питання. (Мені довелося це подивитися)
hectorpal

3

Ще одна річ, крім того, що вже згадували інші люди. У той час як очікується , амортизується складність для вставки елемента до unordered_set представляє собою О (1), то і тоді він буде приймати O (п) , оскільки потреби хеш-таблиці , щоб бути перебудована (кількість ковшів необхідно змінити) - навіть з хороша хеш-функція. Так само, як і вставлення елемента у вектор займає O (n) раз у раз, тому що базовий масив потрібно перерозподілити.

Вставлення в набір завжди займає максимум O (log n). Це може бути кращим у деяких програмах.


3

Вибачте, ще одну річ, яку варто помітити про відсортовану властивість:

Якщо ви хочете діапазон даних у контейнері, наприклад: Ви зберігали час у наборі , і ви хочете час з 2013-01-01 по 2014-01-01.

Для не упорядкованого_набору це неможливо.

Звичайно, цей приклад був би більш переконливим для випадків використання між картою та unororder_map .


3

g++ 6.4 stdlibc ++ впорядковано порівняно з невпорядкованим орієнтиром набору

Я визначив цю домінуючу реалізацію Linux C ++, щоб побачити різницю:

введіть тут опис зображення

Повна детальна інформація та аналіз наведені за адресою: яка основна структура даних STL, встановлена ​​в C ++? і я їх тут не повторюватиму.

"BST" означає "тестований з, std::setа" хеш-карта "означає" перевірений std::unordered_set. "Heap" - це те, std::priority_queueщо я проаналізував у: Heap vs Binary Search Tree (BST)

Короткий підсумок:

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

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

  • криві чітко говорять про те, що впорядкований std::setна основі BST та заснований на std::unordered_setхешмапі. У довідковій відповіді я додатково підтвердив, що шляхом GDB є крок налагодження коду.

Подібне запитання щодо mapvs unordered_map: Чи є якась перевага використання карти над невпорядкованим_мапом у випадку тривіальних ключів?


1

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

Можливо також, що хоча швидше отримати доступ, час для створення індексу або пам'яті, що використовується при його створенні та / або доступі, більший.


+1, позначення Big Oh приховує постійні фактори, а для типових розмірів проблеми це найчастіше важливі постійні фактори.
j_random_hacker

1

Якщо ви хочете сортувати речі, то ви використовували б набір замість unororder_set. unordered_set використовується над набором, коли замовлення зберігається не має значення.


1

Хоча ця відповідь може запізнитися на 10 років, варто зазначити, що std::unordered_setтакож є недоліки безпеки.

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

Це може бути використано для дуже ефективних та елегантних атак на відмову в обслуговуванні.

Багато (більшість?) Реалізацій мов, які внутрішньо використовують хеш-карти, натрапили на це:

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