Что случилось с O (1)?

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

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

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

Хотя я заметил это раньше, пример только что появился в вопрос Pandincus:« Правильная коллекция для использования для получения элементов за время O (1) в C # .NET? ».

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

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

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

ВОПРОС: Вся эта подготовка действительно под вопрос. Что такое небрежность вокруг O (1) и почему ее так слепо принимают? Признается ли, что даже O (1) может быть нежелательно большим, хотя и почти постоянным? Или O (1) просто присвоение понятия вычислительной сложности неформальному использованию? Я озадачен.

ОБНОВЛЕНИЕ: ответы и комментарии указывают на то, что я небрежно определял O (1) сам, и я это исправил. Я все еще ищу хорошие ответы, и в некоторых случаях некоторые из веток комментариев гораздо интереснее, чем их ответы.


person orcmid    schedule 02.12.2008    source источник
comment
Похоже, что ответы на вопросы превратились в обсуждение определения хеш-таблиц, которое, я уверен, рассматривается в другом месте на SO :-). Итак, я иду обедать; увидимся позже.   -  person paxdiablo    schedule 02.12.2008
comment
Я думаю, что это попадает в хэш-таблицы и другие словарные схемы / схемы сбора, потому что это тот пример, на который я указал. Кроме того, похоже, что это место, где O (1) появляется как безоговорочное предположение. Мне нравится, в каком направлении идет дискуссия, в основном, я лучше понимаю ситуацию.   -  person orcmid    schedule 02.12.2008
comment
Недавно я нашел решение проблемы с рюкзаком в O (1), упаковывая свой собственный рюкзак. Универсальный ответ - забудьте об этом, они не влезут, сколько бы вещей вы туда не упаковывали, они никогда не поместятся. И поскольку это проблема типа NP, все другие аналогичные проблемы имеют аналогичные решения O (1) ...   -  person Headcrab    schedule 22.05.2009
comment
Возможно, лучший пример O (1) - чтение любого значения из массива по индексу. var y = x [5]; Это один шаг O (1). Однако поиск определенного значения в массиве будет O (N), потому что элемент может быть где угодно в массиве, и вам нужно искать все элементы (N) массива, таким образом, это O (N). Причина, по которой люди упоминают O (1) с Hash, заключается в том, что в лучшем случае / ожидаемом сценарии элементы индексируются, а поиск по Hash занимает только один шаг O (1), что делает его поиском (прямое чтение). Это количество шагов и игнорирует коллизии и все другие особенности хэша. Big O означает оценку, а не реализацию.   -  person raddevus    schedule 31.12.2020


Ответы (13)


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

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

person ysth    schedule 02.12.2008
comment
O (1) не обязательно постоянный; скорее, он не зависит от рассматриваемых переменных, что, вероятно, является лучшим трюизмом, который я слышал за последнее время. - person Timothy Khouri; 02.12.2008
comment
Но, строго говоря, это неправильно. O (1) означает ограниченный, а не постоянный. Зависимость все еще существует, и вы только уверены, что T (n) ‹M для некоторой (возможно, большой) константы M. T (n) также может строго возрастать с n и по-прежнему O (1). - person Federico A. Ramponi; 02.12.2008
comment
@ Федерико Рампони: это то, что я пытался выразить менее техническими терминами. строгое возрастание практически не имеет значения, если оно ограничено. - person ysth; 02.12.2008
comment
@Timothy Khouri: ysth подчеркивает, что нужно быть осторожным при указании переменных, которые находятся на рассмотрении. Одна из распространенных интерпретаций константы - постоянная w.r.t. все возможные переменные, которые будут включать, среди прочего, тактовую частоту. Ясно, что это то, что мы хотели бы здесь исключить. - person j_random_hacker; 15.06.2010
comment
Этот ответ, кажется, упускает суть. Мы определяем, что такое одна операция; если мы определяем хеш как одну операцию, то O (1) действительно будет константой. Если мы хэшируем целые числа, это правильное определение; если мы хэшируем файлы, это может быть не так, но в этом случае, если бы мы сохранили файлы вместо этого в двоичном дереве, вызов сравнения одной операции также был бы несправедливым. Умножение двух целых чисел обычно считается одной операцией в большинстве алгоритмов, но это, очевидно, бесполезное определение для алгоритмов целочисленного умножения. - person BlueRaja - Danny Pflughoeft; 16.02.2011
comment
Настоящая проблема здесь в том, что поиск в хеш-таблицах - это не худший вариант O (1); это просто амортизируется O (1). См. Ответ Адама Розенфилда. - person BlueRaja - Danny Pflughoeft; 16.02.2011

Проблема в том, что люди очень небрежны с терминологией. Здесь есть 3 важных, но разных класса:

O (1) худший случай

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

O (1) наихудший случай амортизации

Амортизированная означает, что не каждая операция равна O(1) в худшем случае, но для любой последовательности из N операций общая стоимость последовательности не будет O(N) в худшем случае. Это означает, что даже если мы не можем ограничить стоимость какой-либо отдельной операции константой, всегда будет достаточно «быстрых» операций, чтобы компенсировать «медленные» операции, так что время выполнения последовательности операций будет линейным. по количеству операций.

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

O (1) средний случай

Этот самый хитрый. Существует два возможных определения среднего случая: одно для рандомизированных алгоритмов с фиксированными входными данными и одно для детерминированных алгоритмов со случайными входными данными.

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

В другом случае нам нужно распределение вероятностей по входам. Например, если бы мы измеряли алгоритм сортировки, одним из таких распределений вероятностей было бы распределение, которое имеет все N! возможные перестановки входа равновероятны. Тогда среднее время выполнения - это среднее время выполнения по всем возможным входам, взвешенное по вероятности каждого входа.

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

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

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

person Adam Rosenfield    schedule 02.12.2008
comment
Интересно посмотреть на вопрос. Не уверен, почему у вас такая большая синяя область / ссылка. Я думаю, что в вашем изложении амортизированного дела что-то не так. Вы имеете в виду, что это не O (n) в худшем случае? Как насчет O (log n)? Может быть, мы имеем в виду, что амортизированная стоимость все еще ограничена константой выше? - person orcmid; 02.12.2008
comment
Я нервничаю по поводу смешивания O (1) и статистических усилий для определения среднего поведения. Мы могли бы говорить об асимптотическом поведении среднего в терминах большого O. Я видел ситуации, когда ни ключи, ни хэши не были распределены равномерно и обычно не помогают, если они у вас есть. - person orcmid; 02.12.2008
comment
Итак, мораль этой истории такова: используйте крупный жирный текст, если хотите проголосовать за. Я шучу, я шучу. :) - person Kip; 22.05.2009
comment
Нет, мораль этой истории в том, чтобы дать высший ответ на сообщение, на которое есть ссылка в блоге SO =) - person Adam Rosenfield; 22.05.2009
comment
Хорошее объяснение, +1. Одна придирка, однако, для наихудшего случая с амортизацией O (1), я думаю, есть дополнительное ограничение на N, а именно, что оно должно быть достаточно большим. Если мы можем выбрать любое значение для N, мы могли бы просто выбрать N = 1, и это означало бы, что O (1) амортизировано = O (1). - person j_random_hacker; 12.06.2010

O (1) означает постоянное время и (обычно) фиксированное пространство

Чтобы прояснить, это два отдельных утверждения. У вас может быть O (1) во времени, но O (n) в пространстве или что-то еще.

Признается ли, что даже O (1) может быть нежелательно большим, хотя и почти постоянным?

O (1) может быть непрактично ОГРОМНЫМ, но по-прежнему O (1). Часто забывают, что, если вы знаете, что у вас будет очень маленький набор данных, константа важнее, чем сложность, а для достаточно небольших наборов данных это баланс двух. Алгоритм O (n!) Может превзойти алгоритм O (1), если константы и размеры наборов данных имеют соответствующий масштаб.

Обозначение O () - это мера сложности, а не время, которое займет алгоритм, или чистая мера того, насколько «хорош» данный алгоритм для данной цели.

person Draemon    schedule 02.12.2008
comment
Хм, я полагаю, что это верно в отношении пространства, если мы предположим, что n - это количество элементов в коллекции. На самом деле, упоминать о космосе в данном случае, наверное, бесполезно. Стоит ли поправить вопрос, чтобы убрать то? - person orcmid; 02.12.2008
comment
Вы касаетесь другого аспекта, а именно знания соответствующей переменной параметра. Мы говорим O (1), что предполагает f (n) = k, но мы не говорим, что такое n. Интересно, что это за молчаливое предположение, если таковое имеется. - person orcmid; 02.12.2008
comment
Ну, вообще-то, я сказал (обычно) пространство. Я оставлю это как есть. - person orcmid; 02.12.2008
comment
Повторите свой 1-й комментарий - я просто хотел уточнить, что нет причин для того, чтобы пространственная и временная сложность были одинаковыми. Нет необходимости менять Q, если вы этого не хотите. Стоит отметить, что часто приходится выбирать между временем и пространством. - person Draemon; 02.12.2008
comment
Более важно понимать, что каждая операция в ADS может иметь разную сложность во времени и пространстве (вставка или поиск), а также пространство, занимаемое самим ADS. Обычно мы говорим о временной сложности алгоритма / операции и пространственной сложности ADS. - person Draemon; 02.12.2008
comment
Повторите ваш второй комментарий - n обычно очевиден из контекста. Большинство структур данных содержат несколько записей с одинаковым типом, а n - это количество записей. У вас даже может быть несколько переменных, хотя это редко. - person Draemon; 02.12.2008
comment
Педантический комментарий: у вас может быть O (N) времени с O (1) пробелом. У вас не может быть времени O (1) с пространством O (N) (ну ... если вы не выделите запасной буфер только для ударов). Таким образом, если алгоритм занимает O (1) времени, он также должен занимать O (1) пространство. - person Tom; 06.12.2008
comment
@Tom - Я понимаю вашу точку зрения, и это, вероятно, плохой пример, но это не невозможно. Простой алгоритм распределения памяти вполне может быть O (1) во времени и O (N) в пространстве, и я уверен, что есть и другие (возможно, надуманные) примеры. Моя точка зрения заключалась в том, что они не обязательно одинаковы. - person Draemon; 07.12.2008
comment
Даже лучший алгоритм распределения памяти, вероятно, будет O (n) или, по крайней мере, O (log n) - при обновлении таблиц страниц возникают накладные расходы. - person Nick Johnson; 11.12.2008
comment
Конечно, но это теоретически. Как я уже сказал - плохой пример. Я подозреваю, что некоторые легкие / встроенные системы имеют O (1). - person Draemon; 12.12.2008
comment
@Draemon Я нахожу ваш ответ трудным для понимания ... вы имеете в виду, что хэш-таблицы хоть и работают быстро, но требуют много оперативной памяти? - person Pacerier; 02.11.2011

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

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

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

person Bill the Lizard    schedule 02.12.2008
comment
Это может произойти только в том случае, если у вас была действительно ужасная хеш-функция (например: function hash ($ i) {return 1;}) и все ключи разрешены в один и тот же хеш, или вы установили порог 'вырасти', когда есть только 1 свободное пространство, чтобы вырасти только на 1 свободное пространство. Никто этого не делает. - person Kent Fredric; 02.12.2008
comment
Описанная вами хеш-функция - наихудший случай. В лучшем случае это идеальная хеш-функция, которая никогда не сталкивается. Реальность находится где-то посередине, но ближе к совершенству, чем наихудший случай, поэтому мы можем сказать O (1). - person Bill the Lizard; 02.12.2008
comment
Я слышу претензию, но не слышу ее обоснования. Это нет, почему вы можете сказать O (1), если говорите о сложности. Может быть, поэтому люди говорят O (1) как необоснованное утверждение. Как часто кто-нибудь проверяет заявление, чтобы убедиться, что производительность действительно приемлема? - person orcmid; 02.12.2008
comment
Проверить это довольно просто. Вам нужно только протестировать свои хэш-функции, чтобы убедиться, что в ожидаемом диапазоне входных данных очень мало коллизий. Я делаю это для каждого объекта, который собираюсь сохранить в хэше. - person Bill the Lizard; 02.12.2008
comment
Обычно это не наихудший случай, когда O (n) не делает его O (1). Алгоритм следует оценивать по верхней границе его производительности, т.е. в худшем случае. Если вы не хотите определить несколько дополнительных правил для реализации хэша, но даже тогда я говорю, что это только O (n) для малых n. - person Stroboskop; 02.12.2008
comment
Существуют алгоритмы хеширования, которые гарантируют наихудший случай лучше, чем линейный. Хеширование с кукушкой - это O (1) для поиска. Черт возьми, даже использование сбалансированного двоичного дерева вместо связанного списка для коллизий гарантировало бы O (log N), но я не знаю, действительно ли кто-нибудь это делает. - person Tom; 06.12.2008
comment
Если вам разрешено использовать только верхнюю границу, тогда Quicksort будет O (n ^ 2). Технически возможно, но не так полезно, как сказать, что это O (log n) с наихудшим случаем O (n ^ 2). Хеш-таблицы сталкиваются с худшим случаем даже реже, чем быстрая сортировка, поэтому это даже меньшая проблема. - person Nick Johnson; 11.12.2008

Hashtables - это структура данных, которая поддерживает поиск и вставку O (1).

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

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

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

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

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


Давайте посмотрим на пример. Давайте воспользуемся хеш-таблицей для хранения следующих (key, value) пар:

  • (Name, Bob)
  • (Occupation, Student)
  • (Location, Earth)

Мы реализуем серверную часть хеш-таблицы с массивом из 100 элементов.

key будет использоваться для определения элемента массива для хранения пары (key, value). Для определения элемента будет использоваться hash_function:

  • hash_function("Name") возвращает 18
  • hash_function("Occupation") возвращает 32
  • hash_function("Location") возвращает 74.

Исходя из приведенного выше результата, мы назначим (key, value) пар элементам массива.

array[18] = ("Name", "Bob")
array[32] = ("Occupation", "Student")
array[74] = ("Location", "Earth")

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

Точно так же при поиске элемента используется хеш-функция.

Если мы хотим найти ключ "Name", мы выполним hash_function("Name"), чтобы выяснить, в каком элементе массива находится желаемое значение.

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

Все хорошо. Попробуем добавить дополнительную запись ("Pet", "Dog"). Однако возникает проблема, поскольку hash_function("Pet") возвращает 18, что является таким же хешем для ключа "Name".

Следовательно, нам нужно разрешить эту коллизию хешей. Предположим, что использованная нами функция разрешения конфликтов хешей обнаружила, что новый пустой элемент равен 29:

array[29] = ("Pet", "Dog")

Поскольку при этой вставке произошел конфликт хешей, наша производительность была не совсем O (1).

Эта проблема также возникает, когда мы пытаемся найти ключ "Pet", так как попытка найти элемент, содержащий ключ "Pet", путем выполнения hash_function("Pet") всегда будет изначально возвращать 18.

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

person coobird    schedule 02.12.2008
comment
На первый взгляд мне понравилось это объяснение, за исключением того, что оговорка была в конце. Прежде чем вы начнете делать эти смелые заявления, я бы хотел, чтобы предостережение было сделано заранее. На второй взгляд, пример с использованием hash_function является подделкой: нет сохраненных ключей для обнаружения столкновения, неадекватная обработка имени + значения. - person orcmid; 02.12.2008
comment
Этот пример кажется мне настолько вводящим в заблуждение, что я проголосую за этот ответ, если вы его не исправите или он не вызовет достаточно комментариев, чтобы стать полезным объектом обсуждения. Я не хочу голосовать против. - person orcmid; 02.12.2008
comment
Я внес некоторые изменения в ответ на ваши комментарии по поводу предостережений. Надеюсь, это лучше, чем предыдущая версия. - person coobird; 02.12.2008
comment
Прогресс. Вы также не показываете, как хранятся ключи, чтобы вы могли сказать, что у вас есть столкновение. Кроме того, вы можете предположить линейное переполнение, чтобы упростить задачу. В противном случае мы не знаем, откуда взялся второй хеш для коллизии. - person orcmid; 02.12.2008
comment
Ах, спасибо, что указали на ошибку. Это была серьезная оплошность! - person coobird; 02.12.2008
comment
Хорошо, хорошо выглядишь. Вы можете сказать что-нибудь о том, как вы переполнились с 18 до 29, или просто использовать линейное переполнение и использовать 19. Это упрощает описание, и вы можете указать, что существуют более сложные методы переполнения. Спасибо, что подыгрывали. Повествование значительно улучшено. - person orcmid; 05.12.2008
comment
Конечно, если вы используете стандартную библиотечную реализацию хеш-таблицы, она будет автоматически расширяться, так что коллизии будут очень редкими, если вы не соберетесь изо всех сил, чтобы выбрать данные с таким же хеш-кодом. - person Kip; 22.05.2009

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

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

Имейте в виду, это также предполагает, что вычисление хэша равно O (1). Вы можете утверждать, что для строк длиной K любой хэш имеет минимальное значение O (K). На самом деле, вы можете довольно легко связать K, скажем, K ‹1000. O (K) ~ = O (1) для K‹ 1000.

person Tom    schedule 02.12.2008
comment
Спасибо, интересно. Я не видел, чтобы кто-то рекомендовал хеш-функцию Cuckoo или предлагал встроить ее в структуру словарей. Вы знаете, где это было опубликовано. Было бы интересно посмотреть, что нужно для получения разумного O (1) и каковы идеальные условия. - person orcmid; 02.12.2008
comment
itu.dk/people/pagh/papers/cuckoo-jour.pdf - оригинал. В Википедии также есть хорошее описание на en.wikipedia.org/wiki/Cuckoo_hashing - person Tom; 03.12.2008
comment
Это красивая бумага. Я просто быстро просмотрел его и внимательно изучил анализ и сравнения. Похоже, что компромисс заключается в стоимости запуска двух хешей, в том числе для проблемы смещения. Статья в Википедии тоже полезна. Когда-нибудь я должен найти способ попробовать это. - person orcmid; 05.12.2008
comment
Я не думаю, что компромисс заключается в стоимости запуска двух хешей. Кажется, что это неэффективно, когда вы вставляете данные в слот, который заменяется, вставляется в другой занятый слот, который заменяется, вставляется в другой занятый слот ... - person Pacerier; 02.11.2011

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

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

Когда алгоритм занимает время O (1), единственное, что это означает, это то, что для данной функции T (f), которая вычисляет время выполнения функции f (n), существует натуральное положительное число k такое, что T (f) ‹K для любого входа n. По сути, это означает, что верхняя граница времени выполнения алгоритма не зависит от его размера и имеет фиксированный конечный предел.

Это никоим образом не означает, что ограничение невелико, просто оно не зависит от размера входного набора. Поэтому, если я искусственно определю границу k для размера набора данных, его сложность будет O (k) == O (1).

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

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

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

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

person Community    schedule 02.12.2008
comment
Нечестно фиксировать n на константе и затем требовать O (1). Вы можете сделать это для любых вычислений любой сложности. Извините, возьмите флаг на этом спектакле. С другой стороны, ваше обсуждение указывает на то, как часто используется управляющая переменная. Я согласен с тем, что размер набора данных выглядит упрощенно. - person orcmid; 02.12.2008

В общем, я думаю, что люди используют их сравнительно без оглядки на точность. Например, структуры данных на основе хэшей ищут O (1) (в среднем), если они хорошо спроектированы и у вас есть хороший хэш. Если все хешируется в одну корзину, то это O (n). Как правило, хотя используется хороший алгоритм и ключи распределены разумно, поэтому удобно говорить об этом как о O (1) без всяких оговорок. То же самое и со списками, деревьями и т. Д. Мы имеем в виду определенные реализации, и просто удобнее говорить о них, обсуждая общие положения, без оговорок. Если, с другой стороны, мы обсуждаем конкретные реализации, то, вероятно, стоит быть более точным.

person tvanfosson    schedule 02.12.2008

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


Чтобы ответить, почему это уместно: ОП спросил, почему O (1), казалось, был разбросан так небрежно, хотя, по его мнению, он, очевидно, не мог применяться во многих обстоятельствах. Этот ответ объясняет, что время O (1) действительно возможно в этих обстоятельствах.

person Joel Coehoorn    schedule 02.12.2008
comment
Только если это идеальный хеш и никакие два элемента не генерируют одинаковый хеш - тогда он становится O (некоторая функция от n). - person paxdiablo; 02.12.2008
comment
Кроме того, это только в том случае, если ваш хэш-ключ соответствует некоторому ключу в каком-то индексе в памяти. В противном случае вам понадобится какая-то таблица поиска, чтобы выяснить, где находится хешированный элемент. - person Kibbee; 02.12.2008
comment
Подход HashTable, но не гарантируется достижение O (1) - person JaredPar; 02.12.2008
comment
@Kibbee, это все еще O (1), если сама хеш-функция - O (1), поскольку поиск в таблице также не зависит от размера данных - я имею в виду, что это O (1) для немного большего 1 :-) - person paxdiablo; 02.12.2008
comment
@ Тим, ты вообще читал вопросы? Этот ответ, похоже, относится к совершенно другому вопросу :-) - person paxdiablo; 02.12.2008
comment
@Pax - идеальный хэш увеличит стоимость проверки того, что элемент не находится в хеш-таблице. Хеш-таблицы, которые увеличивают количество ведер для поддержания постоянного коэффициента заполнения, в среднем амортизируются до O (1), а не до O (некоторая функция от n). - person Barry Kelly; 02.12.2008
comment
@Barry, возможно, я был неясен - идеальный хеш гарантирует отсутствие коллизий - если у вас есть 3-символьный уникальный 'ключ', содержащий символы 'A' - 'Z', идеальный хеш превратит его в int от 1 до 26 ^ 3 и используйте это как индекс; это определенно O (1). - person paxdiablo; 02.12.2008
comment
И, если вы увеличите количество сегментов, хеш-функция должна измениться, чтобы это учесть. Повторное вычисление всех хэшей в этом случае - O (n). Если вы не измените хэш-функцию, вам придется прибегнуть к цепочке, которая снова станет O (n). - person paxdiablo; 02.12.2008
comment
По сути, хеш-таблицы приближаются к O (1), если вы можете контролировать входные данные и оптимизировать для них хеш-функцию. - person paxdiablo; 02.12.2008
comment
Изменение размера выполняется при вставке, а не при поиске, поэтому пересчет хэшей не засчитывается в счет амортизации времени поиска. - person Bill the Lizard; 02.12.2008
comment
Хорошее обсуждение. Я бы не стал путать возможность (небольшого) постоянного времени доступа с реальностью, но комментарии настолько полезны, что я почти хочу проголосовать за ответ! - person orcmid; 02.12.2008

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

(из-за того, что столкновения ДЕЙСТВИТЕЛЬНО происходят, и при столкновении должно быть назначено другое местоположение)

Кроме того, на практике HashMaps поддерживаются массивами с начальным размером, который «увеличивается» до двойного размера, когда он достигает в среднем 70% заполнения, что дает относительно хорошее адресное пространство. При заполнении 70% частота коллизий растет быстрее.

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

На самом деле есть только один способ создать «идеальную хеш-таблицу» с O (1), и для этого требуется:

  1. Генератор глобального идеального хеш-ключа
  2. Неограниченное адресное пространство.

(Исключительный случай: если вы можете заранее вычислить все перестановки разрешенных ключей для системы, а адресное пространство целевого резервного хранилища определено таким образом, чтобы оно могло содержать все разрешенные ключи, тогда у вас может быть идеальный хеш, но это совершенство "ограниченное доменом")

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

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

Суффиксное примечание Примечание. Я использую здесь O (1.5) и O (2). Их на самом деле не существует в большом количестве. Это просто то, что люди, которые не разбираются в больших вещах, считают логическим обоснованием.

Если что-то требует 1,5 шага, чтобы найти ключ, или 2 шага, чтобы найти этот ключ, или 1 шаг, чтобы найти этот ключ, но количество шагов никогда не превышает 2, и то, занимает ли он 1 шаг или 2, является полностью случайным, тогда это все равно Большой O из O (1). Это потому, что неважно сколько элементов вы добавляете к размеру набора данных, он все равно поддерживает ‹2 шага. Если для всех таблиц> 500 ключей требуется 2 шага, то вы можете предположить, что эти 2 шага на самом деле одношаговые с 2 частями, ... что по-прежнему O (1).

Если вы не можете сделать это предположение, значит, вы вообще не мыслите Большим О, потому что тогда вы должны использовать число, которое представляет количество конечных вычислительных шагов, необходимых для выполнения всего, а «один шаг» для вас бессмысленен. Просто подумайте, что нет НЕТ прямой корреляции между Big-O и количеством задействованных циклов выполнения.

person Kent Fredric    schedule 02.12.2008
comment
Кент, в среднем 1,5 поиска равно O (1), потому что количество поисков не зависит от рассматриваемого n (размера таблицы). Нет O (2). - person Barry Kelly; 02.12.2008
comment
Я пояснил, что Барри, извините, слишком много пытается использовать непрофессиональное мышление - person Kent Fredric; 02.12.2008
comment
По сути, все O (1) означает любой член множества f (n) = k (или набора всех функций, f ‹sub› k ‹/sub› (n) = k). k может быть ни маленьким, ни очень постоянным. Несмотря на все разговоры об идеальных хэшах, я не вижу, чтобы люди их конструировали или даже определяли, позволяет ли это ситуация. - person orcmid; 02.12.2008
comment
@orcmid, я думаю, что люди реалистичны, понимают, что они неправдоподобны, и не пытаются. самый совершенный хэш - это сами данные, и это вряд ли оптимально. - person Kent Fredric; 02.12.2008
comment
@Kent - идеальные хэши могут быть весьма полезны. Во многих случаях данные статичны, но их нужно искать по произвольному ключу. В этих случаях вы можете перефразировать / изменить размер после загрузки всех данных, пока у вас не будет мало / нет коллизий. - person Tom; 06.12.2008

O (1) в точности означает, что временная сложность алгоритма ограничена фиксированным значением. Это не означает, что он постоянный, только то, что он ограничен независимо от входных значений. Строго говоря, многие якобы временные алгоритмы O (1) на самом деле не являются O (1) и просто работают так медленно, что они ограничены для всех практических входных значений.

person Wedge    schedule 02.12.2008

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

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

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

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

Более того, помимо проблемы копирования и компактности, а также разметки и развертки, детали реализации могут существенно повлиять на возникающие сложности:

  1. Инкрементальные сборщики мусора, которые отслеживают грязные биты и т. Д., Могут почти устранить эти более крупные повторные обходы.
  2. Это зависит от того, работает ли ваш GC периодически по времени настенных часов или пропорционально количеству выделенных ресурсов.
  3. Независимо от того, является ли алгоритм стиля метки и развертки параллельным или останавливающим мир
  4. Помечает ли он новые выделения черным цветом, если он оставляет их белыми, пока не сбрасывает их в черный контейнер.
  5. Независимо от того, допускает ли ваш язык модификации указателей, некоторые сборщики мусора могут работать за один проход.

Наконец, обсуждая алгоритм, мы говорим о соломенном человечке. Асимптотика никогда не будет полностью включать все переменные вашей среды. Редко вы когда-либо реализуете каждую деталь структуры данных, как задумано. Вы заимствуете функцию здесь и там, вы вставляете хеш-таблицу, потому что вам нужен быстрый неупорядоченный доступ к ключам, вы используете объединение-поиск по непересекающимся наборам со сжатием пути и объединением по рангу для объединения областей памяти там, потому что вы не можете позволить себе платить стоимость, пропорциональную размеру регионов, когда вы их объединяете или что у вас есть. Эти структуры являются мыслительными примитивами, и асимптотика помогает вам при планировании общих характеристик производительности для структуры «в целом», но знание констант тоже имеет значение.

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

person Edward KMETT    schedule 02.12.2008

Я думаю, когда многие люди используют термин «O (1)», они неявно имеют в виду «маленькую» константу, что бы «малый» ни означало в их контексте.

Вы должны проводить весь этот большой анализ с учетом контекста и здравого смысла. Это может быть как чрезвычайно полезный инструмент, так и нелепый, в зависимости от того, как вы его используете.

person John D. Cook    schedule 02.12.2008