Существуют ли случаи, когда вы предпочитаете более высокий алгоритм сложной сложности по сравнению с более низким?

Существуют ли случаи, когда вы предпочитаете O(log n) сложность по времени для O(1) временной сложности? Или O(n) до O(log n)?

Есть ли у вас примеры?

Ответ 1

Может быть много причин предпочесть алгоритм с более высокой сложностью O-времени по сравнению с более низкой:

  • Большую часть времени труднее достичь более низкой сложности big-O, требующей квалифицированной реализации, большого количества знаний и большого количества тестирования.
  • big-O скрывает детали о константе: алгоритм, который работает в 10^5, лучше с точки зрения big-O, чем 1/10^5 * log(n) (O(1) против O(log(n)), но для большинства разумных n первое будет работать лучше. Например, лучшая сложность для умножения матриц - O(n^2.373) но константа настолько высока, что никакие (насколько мне известно) вычислительные библиотеки не используют ее.
  • big-O имеет смысл, когда вы рассчитываете что-то большое. Если вам нужно отсортировать массив из трех чисел, это не имеет большого значения, используете ли вы алгоритм O(n*log(n)) или O(n^2).
  • иногда преимущество сложности времени в нижнем регистре может быть незначительным. Например, существует дерево танго со структурой данных, которое дает сложность времени O(log log N) для поиска элемента, но есть также двоичное дерево, которое находит то же самое в O(log n). Даже для огромных чисел n = 10^20 разница незначительна.
  • сложность времени это еще не все. Представьте себе алгоритм, который работает в O(n^2) и требует O(n^2) памяти. Это может быть предпочтительнее O(n^3) времени и O(1) пространства, когда n не очень велико. Проблема в том, что вы можете подождать долгое время, но сильно сомневаетесь, что сможете найти достаточно большой объем ОЗУ, чтобы использовать его с вашим алгоритмом.
  • распараллеливание - хорошая функция в нашем распределенном мире. Существуют алгоритмы, которые легко распараллеливаются, а есть алгоритмы, которые вообще не распараллеливаются. Иногда имеет смысл запускать алгоритм на 1000 обычных машинах с более высокой сложностью, чем на одной машине с немного лучшей сложностью.
  • в некоторых местах (безопасность) сложность может быть требованием. Никто не хочет иметь алгоритм хэширования, который может молниеносно быстро хэшировать (потому что тогда другие люди могут обмануть вас намного быстрее)
  • хотя это не связано с переключением сложности, но некоторые функции безопасности должны быть написаны таким образом, чтобы предотвратить атаку по времени. Они в основном остаются в одном классе сложности, но модифицируются таким образом, что всегда требуется худший случай, чтобы что-то сделать. Один пример сравнивает, что строки равны. В большинстве приложений имеет смысл быстро прерываться, если первые байты отличаются, но в безопасности вы все равно будете ждать, пока самый конец не сообщит плохие новости.
  • кто-то запатентовал алгоритм более низкой сложности, и для компании более экономно использовать более высокую сложность, чем платить деньги.
  • некоторые алгоритмы хорошо адаптируются к конкретным ситуациям. Например, сортировка вставкой имеет среднюю сложность по времени O(n^2), хуже, чем быстрая сортировка или сортировка слиянием, но в качестве оперативного алгоритма она может эффективно сортировать список значений по мере их получения (как ввод данных пользователем), где большинство другие алгоритмы могут эффективно работать только с полным списком значений.

Ответ 2

Всегда существует скрытая константа, которая может быть ниже по алгоритму O (log n). Таким образом, он может работать быстрее на практике для реальных данных.

Есть также проблемы с пространством (например, работа на тостерах).

Кроме того, проблема времени разработчика - O (log n) может быть 1000 × проще для реализации и проверки.

Ответ 3

Я удивлен, что никто еще не упомянул приложения, привязанные к памяти.

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

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

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

Ответ 4

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

Ответ 5

Алистра прибила его, но не представила никаких примеров, поэтому я буду.

У вас есть список из 10 000 кодов UPC для вашего магазина. 10-значный UPC, целое число по цене (цена в копейках) и 30 символов описания квитанции.

Подход

O (log N): у вас есть отсортированный список. 44 байта, если ASCII, 84, если Unicode. Альтернативно, рассматривайте UPC как int64, и вы получите 42 и 72 байта. 10 000 записей - в самом высоком случае вы немного под мегабайтом хранилища.

Подход

O (1): не храните UPC, вместо этого вы используете его как запись в массив. В самом низком случае вы просматриваете почти треть терабайта памяти.

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

Ответ 6

Рассмотрим красно-черное дерево. Он имеет доступ, поиск, вставку и удаление O(log n). Сравните с массивом, имеющим доступ к O(1), а остальные операции - O(n).

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

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

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

Ответ 7

Да.

В реальном случае мы выполнили некоторые тесты при выполнении поиска таблиц с помощью коротких и длинных строковых ключей.

Мы использовали std::map, a std::unordered_map с хешем, который сэмплирует не более 10 раз по длине строки (наши ключи имеют тенденцию быть ориентировочными, поэтому это прилично) и хэш, который показывает каждый символ (теоретически уменьшенные столкновения), несортированный вектор, где мы сравниваем ==, и (если я правильно помню) несортированный вектор, в котором мы также храним хеш, сначала сравниваем хэш, а затем сравниваем символы.

Эти алгоритмы варьируются от O(1) (unordered_map) до O(n) (линейный поиск).

Для скромного размера N довольно часто O (n) избивает O (1). Мы подозреваем, что это связано с тем, что контейнеры на основе node потребовали, чтобы наш компьютер больше перескакивал в памяти, в то время как контейнеры на основе линейки не делали.

O(lg n) существует между ними. Я не помню, как это было.

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

На практике для разумного размера n, O(lg n) составляет O(1). Если на вашем компьютере имеется только 4 миллиарда записей, то O(lg n) ограничено выше 32. (lg (2 ^ 32) = 32) (в информатике lg является короткой для логарифмической основы 2).

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

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

Ответ 8

Возможность параллельного выполнения алгоритма.

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

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

Ответ 9

Предположим, вы внедряете черный список во встроенную систему, где числа от 0 до 1 000 000 могут быть внесены в черный список. Это оставляет вам две возможности:

  • Используйте битовый набор из 1 000 000 бит
  • Используйте отсортированный массив целых чисел с черным списком и используйте двоичный поиск для доступа к ним.

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

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

И даже если вы не разрабатываете встроенную систему, в которой недостаточно памяти, я могу увеличить произвольный лимит от 1 000 000 до 1 000 000 000 000 и сделать тот же аргумент. Тогда для битового набора потребуется около 125 ГБ памяти. Наличие гарантированной наихудшей сложности O (1) может не убедить вашего босса предоставить вам такой мощный сервер.

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

Ответ 11

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

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

Общие примеры: hashtables, расширение "списков массивов" (массивы/векторы с динамическим размером a.k.a.).

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

Ответ 12

Более общий вопрос заключается в том, что бывают ситуации, когда алгоритм O(f(n)) следует использовать алгоритму O(g(n)), хотя g(n) << f(n) как n стремится к бесконечности. Как уже говорили другие, ответ явно "да" в случае, когда f(n) = log(n) и g(n) = 1. Иногда да, даже в случае, когда f(n) является полиномиальным, но g(n) является экспоненциальным. Известным и важным примером является метод Simplex Algorithm для решения задач линейного программирования. В 1970-х годах было показано, что O(2^n). Таким образом, его худшее поведение невозможно. Но - его поведение в среднем случае чрезвычайно хорошее, даже для практических проблем с десятками тысяч переменных и ограничений. В 1980-х годах были обнаружены полиномиальные алгоритмы времени (такие как Кармаркарский алгоритм внутренней точки) для линейного программирования, но через 30 лет алгоритм симплексов по-прежнему представляется алгоритмом выбор (за исключением некоторых очень больших проблем). Это по очевидной причине, что поведение в среднем случае часто более важно, чем поведение худшего случая, но также и по более тонкой причине, что симплекс-алгоритм в некотором смысле более информативен (например, информация о чувствительности легче извлечь).

Ответ 13

Чтобы поставить 2 цента в:

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

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

Ответ 14

Существует хороший вариант использования алгоритма O (log (n)) вместо алгоритма O (1), который игнорируют многочисленные другие ответы: неизменность. В хэш-картах O (1) помещает и получает, предполагая хорошее распределение хеш-значений, но они требуют изменчивого состояния. Неизменяемые карты деревьев имеют O (log (n)) puts and gets, что асимптотически медленнее. Однако неизменность может быть достаточно ценной, чтобы компенсировать худшую производительность, и в случае, когда необходимо сохранить несколько версий карты, неизменность позволяет избежать копирования карты, которая является O (n), и, следовательно, может улучшить производительность.

Ответ 15

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

Рассмотрим следующий пример из словаря хакеров, предложив алгоритм сортировки, основанный на Интерпретация множественных миров квантовой механики:

  • Перенесите массив случайным образом с помощью квантового процесса,
  • Если массив не отсортирован, уничтожьте юниверс.
  • Все остальные юниверсы теперь отсортированы [включая ту, в которой вы находитесь].

(Источник: http://catb.org/~esr/jargon/html/B/bogo-sort.html)

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

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

Ответ 16

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

Ответ 17

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

Ответ 18

Добавление к уже хорошим ответам. Практическим примером могут быть индексы Hash vs B-tree в базе данных postgres.

Индексы хеши формируют индекс хеш-таблицы для доступа к данным на диске, тогда как btree, как предлагает название, использует структуру данных Btree.

В режиме Big-O это O (1) vs O (logN).

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

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

Ответ 19

Когда n мал, а O(1) постоянно медленнее.

Ответ 20

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

или

  1. Когда требования к памяти или другим несрочным ресурсам в алгоритме O (1) являются исключительно большими относительно алгоритма O (log n).

Ответ 21

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

Ответ 22

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

Вот несколько примеров от верхней части головы.

  • Хеширование паролей иногда делается произвольно медленным, чтобы затруднить угадывание паролей грубой силой. В этом сообщении информационной безопасности есть пуля о нем (и многое другое).
  • Бит-монета использует контролируемую медленную проблему для сети компьютеров для решения, чтобы "разминировать" монеты. Это позволяет вести валюту с контролируемой скоростью в коллективной системе.
  • Асимметричные шифры (например, RSA) предназначены для того, чтобы сделать дешифрование без ключей намеренно медленным, чтобы предотвратить кого-то другого без частного ключ для взлома шифрования. Алгоритмы предназначены для трещин, надеюсь, O(2^n) время, когда n - длина бит ключа (это грубая сила).

В другом месте в CS, Быстрая сортировка O(n^2) в худшем случае, но в общем случае это O(n*log(n)). По этой причине анализ "Big O" иногда - это не единственное, что вам нужно, анализируя эффективность алгоритма.