Является ли хэш-карта Java действительно O (1)?

Я видел несколько интересных заявлений о SOh-хеш-картах SO и их времени поиска O(1). Может кто-нибудь объяснить, почему это так? Если эти хэш-карты не сильно отличаются от любых алгоритмов хеширования, которые я купил, всегда должен существовать набор данных, содержащий конфликты.

В этом случае поиск будет O(n), а не O(1).

Может ли кто-нибудь объяснить, являются ли они O (1), и если да, то как они это достигают?

Ответ 1

Особенностью HashMap является то, что в отличие от, скажем, сбалансированных деревьев, его поведение является вероятностным. В этих случаях обычно наиболее полезно говорить о сложности с точки зрения вероятности возникновения события наихудшего случая. Для хэш-карты это, конечно же, случай столкновения относительно того, насколько полно картинка. Столкновение довольно легко оценить.

p collision= n/capacity

Таким образом, хэш-карта с даже небольшим количеством элементов, скорее всего, испытает хотя бы одно столкновение. Обозначение Big O позволяет нам делать что-то более убедительное. Заметим, что для любой произвольной фиксированной константы k.

O (n) = O (k * n)

Мы можем использовать эту функцию для улучшения производительности хэш-карты. Мы могли бы вместо этого думать о вероятности не более двух столкновений.

p столкновение x 2= (n/capacity) 2

Это намного ниже. Поскольку стоимость обработки одного дополнительного столкновения не имеет отношения к производительности Big O, мы нашли способ повысить производительность без фактического изменения алгоритма! Мы можем обобщить это на

p столкновение x k= (n/capacity) k

И теперь мы можем игнорировать какое-то произвольное число столкновений и заканчивать с исчезающе крошечной вероятностью большего количества столкновений, чем мы учитываем. Вы можете получить вероятность на произвольно крошечный уровень, выбрав правильный k, все без изменения фактической реализации алгоритма.

Мы говорим об этом, говоря, что хэш-карта имеет O (1) доступ с высокой вероятностью

Ответ 2

Кажется, вы смешиваете наихудшее поведение со средним (ожидаемым) временем выполнения. Первый действительно является O (n) для хеш-таблиц вообще (т.е. Не использует идеальное хеширование), но это редко бывает актуальным на практике.

Любая надежная реализация хеш-таблицы в сочетании с полупорядочным хешем имеет производительность поиска O (1) с очень небольшим коэффициентом (фактически, 2) в ожидаемом случае в пределах очень небольшого разброса.

Ответ 3

В Java, HashMap работает, используя hashCode, чтобы найти ведро. Каждое ведро представляет собой список предметов, находящихся в этом ковше. Элементы сканируются с использованием равных для сравнения. При добавлении элементов, HashMap изменяется, как только достигается определенный процент загрузки.

Таким образом, иногда это должно сравниться с несколькими элементами, но в целом оно намного ближе к O (1), чем O (n). Для практических целей это все, что вам нужно знать.

Ответ 4

Помните, что o (1) не означает, что каждый поиск проверяет только один элемент - это означает, что среднее количество проверенных элементов остается постоянным w.r.t. количество элементов в контейнере. Поэтому, если для поиска элемента в контейнере со 100 элементами требуется в среднем 4 сравнения, он также должен брать в среднем 4 сравнения, чтобы найти элемент в контейнере со 10000 элементами и для любого другого количества элементов (всегда есть бит дисперсии, особенно вокруг точек, в которых хеш-таблица перерисовывается, и когда имеется очень небольшое количество элементов).

Таким образом, столкновение не мешает контейнеру выполнять операции o (1), если среднее количество ключей на ведро остается в пределах фиксированной привязки.

Ответ 5

Я знаю, что это старый вопрос, но на самом деле есть новый ответ.

Вы правы, что хэш-карта на самом деле не является O(1), строго говоря, поскольку, поскольку количество элементов становится сколь угодно большим, в конечном итоге вы не сможете искать в постоянное время (и определяется O-нотация в терминах чисел, которые могут быть сколь угодно большими).

Но из этого не следует, что сложность реального времени O(n) - потому что нет правила, в котором говорится, что ведра должны быть реализованы как линейный список.

Фактически, Java 8 реализует ведра как TreeMaps, когда они превышают пороговое значение, что делает фактическое время O(log n).

Ответ 6

Если количество ведер (вызов b) остается постоянным (обычный случай), то поиск на самом деле равен O (n).
По мере того как число n становится большим, количество элементов в каждом ковше в среднем равно n/b. Если разрешение столкновения выполняется одним из обычных способов (например, связанный список), то поиск равен O (n/b) = O (n).

Обозначение O - это то, что происходит, когда n становится больше и больше. Он может вводить в заблуждение при применении к определенным алгоритмам, и хэш-таблицы являются примером. Мы выбираем количество ведер, исходя из количества элементов, с которыми мы рассчитываем иметь дело. Когда n примерно такого же размера, как и b, поиск выполняется примерно постоянным временем, но мы не можем назвать его O (1), поскольку O определяется в терминах предела при n → ∞.

Ответ 7

O(1+n/k) где k - количество ковшей.

Если реализация устанавливает k = n/alpha, то она O(1+alpha) = O(1), так как alpha является константой.

Ответ 8

Мы установили, что стандартное описание поиска хеш-таблицы, являющееся O (1), относится к ожидаемому среднему ожидаемому времени, а не к строгому наихудшему результату. Для хеш-таблицы, разрешающей столкновений с цепочкой (например, хэш файл Java), это технически O (1 + α) с хороший хеш функция, где α - коэффициент загрузки таблицы. Все еще постоянный, пока количество объектов, которые вы храните, не больше, чем постоянный множитель, превышающий размер таблицы.

Также объяснялось, что, строго говоря, можно построить вход, требующий O (n) поиска для любой детерминированной хэш-функции. Но также интересно рассмотреть наихудшее ожидаемое время, которое отличается от среднего времени поиска. Используя цепочку, это O (1 + длина самой длинной цепочки), например Θ (log n/log log n), когда α = 1.

Если вас интересуют теоретические способы достижения ожидаемого наихудшего поиска по времени, вы можете прочитать о динамическое идеальное хеширование, которое рекурсивно решает конфликты с другой хэш-таблицей!

Ответ 9

Это O (1), только если ваша хеширующая функция очень хорошая. Реализация хеш-таблицы Java не защищает от плохих хеш-функций.

Если вам нужно вырастить таблицу при добавлении элементов или нет, это не относится к вопросу, потому что речь идет о времени поиска.

Ответ 10

В основном это относится к большинству реализаций хэш-таблиц на большинстве языков программирования, так как сам алгоритм не меняется.

Если в таблице нет столкновений, вам нужно сделать только один просмотр, поэтому время работы - O (1). Если присутствуют коллизии, вам нужно выполнить несколько операций поиска, что снижает производительность по сравнению с O (n).

Ответ 11

Это зависит от алгоритма, который вы выбираете, чтобы избежать столкновений. Если ваша реализация использует отдельную цепочку, тогда сценарий наихудшего случая происходит, когда каждый элемент данных хэшируется до одного значения (например, плохой выбор хеш-функции). В этом случае поиск данных ничем не отличается от линейного поиска в связанном списке, то есть O (n). Однако вероятность того, что это происходит, ничтожно мала, и поиск лучше, а средние случаи остаются постоянными, т.е. O (1).

Ответ 12

Академики в стороне, с практической точки зрения, HashMaps следует воспринимать как несущественное влияние на производительность (если только ваш профилировщик не говорит об этом иначе).

Ответ 13

Только в теоретическом случае, когда хэш-коды всегда разные, и ведро для каждого хэш-кода также отличается, O (1) будет существовать. В противном случае он имеет постоянный порядок, т.е. При приращении хэшмапа, его порядок поиска остается постоянным.

Ответ 14

Элементы внутри HashMap хранятся в виде массива связанного списка (node), каждый связанный список в массиве представляет собой ведро для уникального хеш-значения одного или нескольких ключей.
Добавляя запись в HashMap, хэш-код ключа используется для определения местоположения ведра в массиве, например:

location = (arraylength - 1) & keyhashcode

Здесь и представляет собой побитовый оператор AND.

Например: 100 & "ABC".hashCode() = 64 (location of the bucket for the key "ABC")

Во время операции get он использует тот же самый способ определения местоположения ведра для ключа. В лучшем случае каждый хэш-код является уникальным и приводит к уникальному ведру для каждого ключа, в этом случае метод get тратит время только на определение местоположения ковша и получение значения, которое является постоянным O (1).

В худшем случае все ключи имеют один и тот же хэш-код и хранятся в одном и том же ведре, это приводит к обходу всего списка, который приводит к O (n).

В случае java 8 ведро Linked List заменяется TreeMap, если размер увеличивается до более 8, это снижает эффективность поиска наихудшего случая до O (log n).

Ответ 15

Конечно, производительность хэш-карты будет зависеть от качества функции hashCode() для данного объекта. Однако, если функция реализована так, что вероятность столкновения очень низкая, она будет иметь очень хорошую производительность (это не строго O (1) во всех возможных случаях, но это в большинстве случаев).

Например, реализация по умолчанию в Oracle JRE - это использовать случайное число (которое хранится в экземпляре объекта так, чтобы оно не менялось, но оно также отключает смещенную блокировку, но это другое обсуждение), поэтому вероятность столкновений очень низкая.