Недавний разговор о unordered_map
C ++ заставил меня понять, что я должен использовать unordered_map
в большинстве случаев, где я использовал map
раньше, из-за эффективности поиска ( амортизированный O (1) против O (log n) ). В большинстве случаев я использую карту, я использую int
или std::string
как тип ключа; следовательно, у меня нет проблем с определением хеш-функции. Чем больше я думал об этом, тем больше я осознавал, что не могу найти никаких причин использования std::map
over std::unordered_map
в случае ключей с простыми типами - я посмотрел на интерфейсы и не нашел ни одного значительные различия, которые повлияют на мой код.
Отсюда вопрос: есть ли реальная причина для использования std::map
более чем std::unordered_map
в случае простых типов , как int
и std::string
?
Я спрашиваю со строгой точки зрения программирования - я знаю, что это не полностью считается стандартом, и что это может создать проблемы с портированием.
Кроме того, я ожидаю, что один из правильных ответов может быть «он более эффективен для небольших наборов данных» из-за меньших издержек (это правда?) - поэтому я хотел бы ограничить вопрос случаями, когда количество Ключи нетривиальны (> 1 024).
Изменить: да , я забыл очевидное (спасибо GMan!) - да, карты, конечно, заказаны - я знаю это, и ищу по другим причинам.
источник
Ответы:
Не забывайте, что
map
держит свои элементы в порядке. Если вы не можете отказаться от этого, очевидно, вы не можете использоватьunordered_map
.Что еще нужно иметь в виду, это то, что
unordered_map
обычно используется больше памяти.map
просто есть несколько указателей на ведение домашнего хозяйства и память для каждого объекта. Наоборот,unordered_map
имеет большой массив (в некоторых реализациях он может быть довольно большим), а затем дополнительную память для каждого объекта. Если вам нужно быть осведомленным о памяти,map
лучше доказать, потому что не хватает большого массива.Итак, если вам нужен чистый поиск-поиск, я бы сказал,
unordered_map
что это путь. Но всегда есть компромиссы, и если вы не можете их себе позволить, то вы не можете их использовать.Исходя из личного опыта, я обнаружил огромное улучшение производительности (измеряемой, конечно) при использовании
unordered_map
вместоmap
таблицы поиска основной сущности.С другой стороны, я обнаружил, что при многократном вставлении и удалении элементов было намного медленнее. Это отлично подходит для относительно статичной коллекции элементов, но если вы делаете тонны вставок и удалений, хэширование + сегментирование, похоже, складываются. (Обратите внимание, это было в течение многих итераций.)
источник
unordered_map
и резервируете его на старте - вы все равно платите штраф за многие вставки? Скажем, вы только один раз вставляете, когда строите таблицу поиска, а потом только читаете из нее.Если вы хотите сравнить скорость вашей реализации
std::map
иstd::unordered_map
реализации, вы можете использовать проект sparsehash от Google, в котором есть программа time_hash_map для их измерения. Например, с gcc 4.4.2 в системе Linux x86_64источник
Я бы повторил примерно ту же мысль, которую сделал GMan: в зависимости от типа использования
std::map
может быть (и часто) быстрее, чемstd::tr1::unordered_map
(используя реализацию, включенную в VS 2008 SP1).Есть несколько усложняющих факторов, которые нужно иметь в виду. Например, в
std::map
, вы сравниваете ключи, что означает, что вы только когда-либо просматриваете достаточно начала ключа, чтобы различить правую и левую ветви дерева. По моему опыту, почти единственный раз, когда вы смотрите на весь ключ, это если вы используете что-то вроде int, которое вы можете сравнить в одной инструкции. С более типичным типом ключа, таким как std :: string, вы часто сравниваете всего несколько символов или около того.Приличная хэш-функция, напротив, всегда смотрит на весь ключ. Таким образом, даже если поиск в таблице имеет постоянную сложность, сам хеш имеет примерно линейную сложность (хотя по длине ключа, а не по количеству элементов). С длинными строками в качестве ключей, оператор
std::map
может завершить поиск, прежде чемunordered_map
он даже начнет свой поиск.Во-вторых, хотя существует несколько методов изменения размера хеш-таблиц, большинство из них довольно медленные - до такой степени, что если поиск не происходит значительно чаще, чем вставки и удаления, std :: map часто будет быстрее, чем
std::unordered_map
.Конечно, как я уже упоминал в комментарии к вашему предыдущему вопросу, вы также можете использовать таблицу деревьев. Это имеет как преимущества, так и недостатки. С одной стороны, он ограничивает наихудший случай деревом. Это также позволяет быстро вставлять и удалять, потому что (по крайней мере, когда я это сделал) я использовал таблицу фиксированного размера. Исключение всех размеров таблицы позволяет вам сохранять хэш-таблицу намного проще и, как правило, быстрее.
Еще один момент: требования к хешированию и древовидным картам разные. Хеширование, очевидно, требует хеш-функции и сравнения на равенство, где упорядоченные карты требуют сравнения меньше, чем. Конечно, гибрид, о котором я говорил, требует и того, и другого. Конечно, для обычного случая использования строки в качестве ключа это на самом деле не проблема, но некоторые типы ключей подходят для упорядочивания лучше, чем хеширование (или наоборот).
источник
dynamic hashing
техники, которая заключается в наличии переходного периода, когда каждый раз, когда вы вставляете элемент, вы также перефразируетеk
другие элементы. Конечно, это означает, что во время перехода вы должны искать 2 разных таблицы ...unordered_map
необходимо подтвердить совпадение хеша с помощью полного сравнения, поэтому все зависит от того, какие части процесса поиска вы сравниваете.Я был заинтригован ответом @Jerry Coffin, который предположил, что упорядоченная карта будет демонстрировать увеличение производительности на длинных строках, после некоторого эксперимента (который можно загрузить из pastebin ), я обнаружил, что это, похоже, справедливо только для коллекций случайных строк, когда карта инициализируется с помощью отсортированного словаря (который содержит слова со значительным количеством префиксов с перекрытием), это правило нарушается, предположительно из-за увеличенной глубины дерева, необходимой для получения значения. Результаты показаны ниже, 1-й числовой столбец - время вставки, 2-й - время выборки.
источник
std::map
как правило, выигрываетstd::unordered_map
, особенно для целочисленных ключей, но ~ 100 ключей, кажется, теряет преимущество иstd::unordered_map
начинает выигрывать. Вставка уже упорядоченной последовательности в astd::map
очень плохая, вы получите худший вариант (O (N)).Я хотел бы просто отметить, что ... есть много видов
unordered_map
s.Посмотрите статью в Википедии на хэш-карте. В зависимости от того, какая реализация была использована, характеристики с точки зрения поиска, вставки и удаления могут значительно отличаться.
И это то, что меня больше всего беспокоит добавление
unordered_map
в STL: им придется выбирать конкретную реализацию, так как я сомневаюсь, что они пойдут дальшеPolicy
, и поэтому мы застрянем с реализацией для среднего использования и ничего для другие случаи ...Например, некоторые хеш-карты имеют линейную перефразировку, где вместо перефразирования всей хэш-карты сразу перефразируется при каждой вставке, что помогает амортизировать стоимость.
Другой пример: некоторые хеш-карты используют простой список узлов для корзины, другие используют карту, другие не используют узлы, но находят ближайший слот, и, наконец, некоторые используют список узлов, но переупорядочивают его так, чтобы последний доступный элемент находится на фронте (как кеширование).
Поэтому на данный момент я предпочитаю
std::map
или, возможно,loki::AssocVector
(для замороженных наборов данных).Не поймите меня неправильно, я хотел бы использовать
std::unordered_map
и я, возможно, в будущем, но трудно «доверять» переносимости такого контейнера, когда вы думаете обо всех способах его реализации и различных результатах, которые в результате этого.источник
Существенные различия, которые не были должным образом упомянуты здесь:
map
сохраняет итераторы для всех элементов стабильными, в C ++ 17 вы даже можете перемещать элементы из одногоmap
в другой, не делая для них итераторы недействительными (и при правильной реализации без какого-либо потенциального размещения).map
сроки для отдельных операций, как правило, более согласованы, поскольку они никогда не требуют больших выделений.unordered_map
использование,std::hash
как реализовано в libstdc ++, уязвимо для DoS, если подается с ненадежным вводом (он использует MurmurHash2 с постоянным начальным числом - не то, чтобы начальное заполнение действительно помогло, см. https://emboss.github.io/blog/2012/12/14/ взлом-ропот-хэш-флуд-душ-перезагрузка / )источник
Хеш-таблицы имеют более высокие константы, чем обычные реализации карт, которые становятся значимыми для небольших контейнеров. Максимальный размер составляет 10, 100, а может, даже 1000 или больше? Константы такие же, как и всегда, но O (log n) близко к O (k). (Помните, логарифмическая сложность все еще действительно хороша.)
Что делает хорошую хеш-функцию зависит от характеристик ваших данных; так что если я не планирую смотреть на пользовательскую хеш-функцию (но, конечно, могу передумать позже, и легко, так как я набираю чертовски близко ко всему), и даже если для многих источников данных выбраны значения по умолчанию, я нахожу упорядоченные природа карты будет достаточно помощи изначально, что я все еще по умолчанию для отображения, а не хеш-таблицы в этом случае.
Кроме того, вам не нужно даже думать о написании хеш-функции для других (обычно UDT) типов, а просто написать op <(что вы в любом случае хотите).
источник
map
и одну из нихunordered_map
, с определенной платформой и определенным размером кэша, и провести комплексный анализ. : PПричины были приведены в других ответах; здесь другое.
Операции std :: map (сбалансированное двоичное дерево) амортизируются O (log n) и наихудшим O (log n). Операции std :: unordered_map (hash table) амортизируются O (1) и наихудшим O (n).
На практике это проявляется в том, что хэш-таблица «икает» время от времени с помощью операции O (n), что может или не может быть тем, что ваше приложение может терпеть. Если это не терпит, вы бы предпочли std :: map вместо std :: unordered_map.
источник
Резюме
Предполагая, что заказ не важен:
std::unordered_map
std::map
. Это потому что читает на немO(log n)
.std::map
это хороший вариант.std::unordered_map
.Исторический контекст
В большинстве языков неупорядоченная карта (словари, основанные на хэше) являются картой по умолчанию, однако в C ++ вы получаете упорядоченную карту в качестве карты по умолчанию. Как это произошло? Некоторые люди ошибочно полагают, что комитет C ++ принял это решение в своей уникальной мудрости, но правда, к сожалению, более ужасна.
Широко мнение, что в C ++ по умолчанию используется упорядоченная карта, потому что параметров их реализации не так уж много. С другой стороны, реализациям на основе хешей есть о чем поговорить. Таким образом, чтобы избежать тупиков в стандартизации, они просто ладили с упорядоченной картой. Приблизительно в 2005 году многие языки уже имели хорошие реализации реализации, основанной на хэше, и поэтому комитету было легче принимать новые
std::unordered_map
. В идеальном миреstd::map
был бы неупорядочен и у нас был быstd::ordered_map
как отдельный тип.Представление
Ниже два графика должны говорить сами за себя ( источник ):
источник
Недавно я сделал тест, который делает 50000 слияния и сортировки. Это означает, что если строковые ключи совпадают, объедините байтовую строку. И окончательный вывод должен быть отсортирован. Так что это включает поиск каждой вставки.
Для
map
реализации требуется 200 мс для завершения работы. Дляunordered_map
+map
требуется 70 мс дляunordered_map
вставки и 80 мс дляmap
вставки. Таким образом, гибридная реализация на 50 мс быстрее.Мы должны дважды подумать, прежде чем использовать
map
. Если вам нужно только отсортировать данные в конечном результате вашей программы, лучше использовать гибридное решение.источник
Небольшое дополнение ко всему вышеперечисленному:
Лучше использовать
map
, когда вам нужно получить элементы по диапазону, так как они отсортированы, и вы можете просто перебирать их от одной границы к другой.источник
От: http://www.cplusplus.com/reference/map/map/
«Внутренне элементы в карте всегда сортируются по ее ключу в соответствии с определенным строгим критерием слабого упорядочения, указанным его внутренним объектом сравнения (типа« Сравнить »).
Контейнеры map обычно медленнее, чем контейнеры unordered_map, для доступа к отдельным элементам по их ключу, но они допускают прямую итерацию для подмножеств в зависимости от их порядка ».
источник