Чи є випадки, коли ви віддаєте перевагу O(log n)
складності O(1)
часу перед часовою складністю? Або O(n)
до O(log n)
?
Чи є у вас приклади?
Чи є випадки, коли ви віддаєте перевагу O(log n)
складності O(1)
часу перед часовою складністю? Або O(n)
до O(log n)
?
Чи є у вас приклади?
Відповіді:
Може бути багато причин віддати перевагу алгоритму з більш високою складністю часу O над нижньою:
10^5
, з точки зору big-O кращий, ніж 1/10^5 * log(n)
( O(1)
vs O(log(n)
), але для більшості розумних n
перший буде краще. Наприклад, найкраща складність для множення матриць є, O(n^2.373)
але константа настільки велика, що жодна ( наскільки мені відомо) бібліотека обчислювальної техніки не використовує її.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 насправді не велике. Проблема полягає в тому, що ви можете довго чекати, але дуже сумніваєтесь, що можете знайти оперативну пам’ять, достатньо велику, щоб використовувати її з алгоритмомO(n^2)
, гіршу, ніж кваксорт або злиття, але як онлайн-алгоритм може ефективно сортувати список значень за їх отриманням (як введення користувача), де більшість інших алгоритмів можуть лише ефективно працювати на повний перелік цінностей.Завжди є прихована константа, яка може бути нижчою за алгоритмом O (log n ). Таким чином, це може працювати швидше на практиці для реальних даних.
Існують також проблеми з космосом (наприклад, біг на тостері).
Існує також проблема часу розробника - O (журнал n ) може бути на 1000 × простішим у впровадженні та перевірці.
lg n
це так, так, настільки близько до k
великих, n
що більшість операцій ніколи не помітить різниці.
Я здивований, що ще ніхто не згадав додатки, пов'язані з пам'яттю.
Може бути алгоритм, який має менше операцій з плаваючою точкою або через його складність (тобто O (1) < O (log n )), або через те, що константа перед складністю менша (тобто 2 n 2 <6 n 2 ) . Незалежно від того, ви все ще можете віддати перевагу алгоритму з більшою кількістю FLOP, якщо нижній алгоритм FLOP більше пов'язаний з пам'яттю.
Що я маю на увазі під "обмеженою пам'яттю", це те, що ви часто отримуєте доступ до даних, які постійно перебувають поза кешем. Для того, щоб отримати ці дані, вам доведеться витягнути пам'ять з власне простору пам’яті в кеш, перш ніж ви зможете виконати свою операцію над нею. Цей крок отримання часто буває досить повільним - набагато повільніше, ніж сама ваша операція.
Отже, якщо ваш алгоритм вимагає більше операцій (але ці операції виконуються на даних, які вже є в кеші [і, отже, не потрібно видобувати дані]), він все одно буде виконувати ваш алгоритм з меншою кількістю операцій (які потрібно виконувати на поза Дані кешування [і, отже, потребують отримання]) з точки зору фактичного стінного часу.
O(logn)
більш O(1)
. Ви можете дуже легко уявити ситуацію, коли за всіх ваших можливих n
, менш обмеженою пам’яттю додаток працюватиме в швидшій стіні, навіть при більшій складності.
У контекстах, де безпека даних викликає занепокоєння, більш складний алгоритм може бути кращим перед менш складним алгоритмом, якщо складніший алгоритм має кращу стійкість до атак часу .
(n mod 5) + 1
, він все O(1)
ще розкриває інформацію про n
. Отже, більш складний алгоритм із плавнішим часом виконання може бути кращим, навіть якщо він може бути асимптотичним (а можливо, навіть на практиці) повільнішим.
Алістра прибила це, але не змогла надати жодних прикладів, тому я буду.
У вас є список з 10 000 UPC-кодів, які продає ваш магазин. 10 цифр UPC, ціле число (ціна в копійках) та 30 символів опису для отримання.
O (log N) підхід: у вас відсортований список. 44 байти, якщо ASCII, 84, якщо Unicode. По черзі, поводьтеся з UPC як з int64, і ви отримаєте 42 та 72 байти. 10 000 записів - у найвищому випадку ви дивитесь трохи під мегабайт пам’яті.
Підхід O (1): Не зберігайте UPC, а використовуйте його як запис у масив. У нижньому випадку ви переглядаєте майже третину терабайт пам’яті.
Який підхід ви використовуєте, залежить від вашого обладнання. У більшості будь-якої розумної сучасної конфігурації ви будете використовувати підхід журналу N. Я можу уявити, що другий підхід є правильною відповіддю, якщо ви з якихось причин працюєте в середовищі, де оперативної пам’яті критично мало, але у вас є маса зберігання. Третина терабайт на диску - це не велика справа, отримання даних в одному зонді диска чогось варте. Простий бінарний підхід в середньому займає 13. (Однак зауважте, що кластеризуючи ваші ключі, ви можете звести це до гарантовано 3-х прочитаних, і на практиці ви б кешували перше.)
malloc(search_space_size)
та підписуватись на те, що він повертається, так само просто, як це отримується.
Розгляньте червоно-чорне дерево. Він має доступ, пошук, вставлення та видалення O(log n)
. Порівняйте з масивом, до якого є доступ, O(1)
і решта операцій є O(n)
.
Отже, враховуючи додаток, де ми вставляємо, видаляємо чи шукаємо частіше, ніж ми отримуємо доступ, і вибір між цими двома структурами, ми віддаємо перевагу червоно-чорному дереву. У цьому випадку ви можете сказати, що ми віддаємо перевагу більш громіздкому O(log n)
часу доступу червоно-чорного дерева .
Чому? Тому що доступ не є нашою головною проблемою. Ми робимо компроміс: на ефективність нашого додатку сильніше впливають інші фактори, ніж цей. Ми дозволяємо цьому конкретному алгоритму страждати від продуктивності, оскільки ми отримуємо великі вигоди, оптимізуючи інші алгоритми.
Тож відповідь на ваше запитання полягає в наступному: коли темп зростання алгоритму не те, що ми хочемо оптимізувати , коли ми хочемо оптимізувати щось інше. Усі інші відповіді - це особливі випадки. Іноді ми оптимізуємо час виконання інших операцій. Іноді ми оптимізуємо для пам'яті. Іноді ми оптимізуємо для безпеки. Іноді ми оптимізуємо ремонтопридатність. Іноді ми оптимізуємо час розробки. Навіть переважаюча константа, яка є достатньо низькою для значення, оптимізує час виконання, коли ви знаєте, що швидкість росту алгоритму не має найбільшого впливу на час виконання. (Якщо ваш набір даних знаходився поза цим діапазоном, ви б оптимізували показник зростання алгоритму, оскільки це врешті-решт домінуватиме над постійною.) Все має вартість, і в багатьох випадках ми торгуємо витратами на більш високий темп зростання алгоритм оптимізації чогось іншого.
O(log n)
"червоно-чорне дерево"? Вставлення 5
в позицію 2 масиву [1, 2, 1, 4]
призведе до [1, 2, 5, 1 4]
(елемент 4
отримає індекс оновлений від 3 до 4). Як ви збираєтеся домогтися такої поведінки у O(log n)
"червоно-чорному дереві", яке ви називаєте "відсортованим списком"?
Так.
У реальному випадку ми провели кілька тестів на пошук таблиць за допомогою коротких та довгих рядкових клавіш.
Ми використовували х std::map
, std::unordered_map
хеш, який вибирає максимум 10 разів по довжині рядка (наші ключі, як правило, орієнтовані на орієнтир, тому це пристойно), і хеш, який відбирає кожен символ (теоретично зменшує зіткнення), несортований вектор, де ми робимо ==
порівняння, і (якщо я правильно пам’ятаю) несортований вектор, де ми також зберігаємо хеш, спочатку порівнюємо хеш, а потім порівнюємо символи.
Ці алгоритми варіюються від O(1)
(unorряд_map) до O(n)
(лінійний пошук).
Для скромних розмірів N досить часто O (n) обіграє O (1). Ми підозрюємо, що це тому, що контейнери на основі вузлів вимагають, щоб наш комп'ютер більше стрибав у пам'яті, тоді як лінійні контейнери цього не робили.
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) (як хеш-карти) можуть легко мати аналогічний або більший постійний коефіцієнт.
Можливість паралельного виконання алгоритму.
Я не знаю , якщо є приклад для класів O(log n)
і O(1)
, але для деяких проблем, ви вибираєте алгоритм з більш високим класом складності , коли алгоритм простіше виконуватися паралельно.
Деякі алгоритми не можуть бути паралельними, але мають настільки низький клас складності. Розглянемо інший алгоритм, який досягає того ж результату і може легко паралелізуватися, але має більш високий клас складності. Коли виконується на одній машині, другий алгоритм повільніше, але при виконанні на кількох машинах реальний час виконання стає все меншим та меншим, тоді як перший алгоритм не може прискорити роботу.
Скажімо, ви впроваджуєте чорний список у вбудованій системі, де цифри від 0 до 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) обіграє їх усіх на практиці.
Моя відповідь тут Швидкий випадковий зважений вибір у всіх рядках стохастичної матриці є прикладом, коли алгоритм зі складністю O (m) швидший, ніж алгоритм зі складністю O (log (m)), коли m
він не надто великий.
Люди вже відповіли на ваше точне запитання, тож я торкнуся дещо іншого питання, про яке люди насправді можуть задуматися, приїжджаючи сюди.
Багато алгоритмів "О (1) часу" та структур даних насправді приймають лише очікуваний час O (1), тобто середній час роботи O (1), можливо, лише за певних припущень.
Поширені приклади: хештелі, розширення "списків масивів" (також масиви / вектори, що мають динамічний розмір).
У таких сценаріях ви можете скористатись структурами даних або алгоритмами, час яких гарантовано є абсолютно обмеженими логарифмічно, хоча вони можуть працювати в середньому гірше.
Тому прикладом може бути збалансоване дерево бінарного пошуку, час роботи якого в середньому гірший, але в гіршому випадку кращий.
Більш загальне питання, якщо є ситуації , в яких один волів би O(f(n))
алгоритм в O(g(n))
алгоритм , хоча , g(n) << f(n)
як n
прагне до нескінченності. Як уже згадували інші, відповідь однозначно "так" у випадку, коли f(n) = log(n)
і g(n) = 1
. Іноді так, навіть у тому випадку, коли f(n)
це многочлен, але g(n)
є експоненціальним. Відомий і важливий приклад - алгоритм симплекса для вирішення задач лінійного програмування. У 1970-х роках це було показано O(2^n)
. Таким чином, його гірша поведінка є нездійсненною. Але - його середня поведінка у випадку надзвичайно хороша, навіть для практичних проблем із десятками тисяч змінних та обмежень. У 1980-х рр. Поліноміальні алгоритми часу (такіБуло відкрито алгоритм Карманкара для внутрішніх точок для лінійного програмування, але через 30 років алгоритм симплекс все ще здається алгоритмом вибору (за винятком певних дуже великих проблем). Це з очевидної причини, що поведінка в середньому випадку часто важливіша, ніж поведінка в гіршому випадку, а також з більш тонкої причини, що алгоритм симплексу в деякому сенсі є більш інформативним (наприклад, інформацію про чутливість легше витягти).
Щоб покласти мої 2 копійки в:
Іноді алгоритм гіршої складності вибирається замість кращого, коли алгоритм працює в певному апаратному середовищі. Припустимо, наш алгоритм O (1) не послідовно отримує доступ до кожного елемента дуже великого масиву фіксованого розміру, щоб вирішити нашу проблему. Потім покладіть цей масив на механічний жорсткий диск або магнітну стрічку.
У цьому випадку алгоритм O (logn) (припустимо, він отримує доступ до диска послідовно), стає більш сприятливим.
Існує хороший випадок використання алгоритму O (log (n)) замість алгоритму O (1), який численні інші відповіді ігнорували: незмінність. Хеш-карти мають O (1) ставить і отримує, припускаючи хороший розподіл хеш-значень, але вони вимагають змінного стану. На незмінних картах дерев O (log (n)) ставить і отримує, що асимптотично повільніше. Однак незмінність може бути досить цінною, щоб компенсувати більш низьку продуктивність, і у випадку, коли потрібно зберегти кілька версій карти, незмінність дозволяє уникнути необхідності копіювання карти, яка є O (n), і, отже, може покращитись виконання.
Просто: Тому що коефіцієнт - витрати, пов’язані з налаштуванням, зберіганням та часом виконання цього кроку - може бути значно, значно більшим із меншою проблемою big-O, ніж з більшою. Big-O - це лише міра масштабованості алгоритмів .
Розглянемо наступний приклад із словника Хекера, пропонуючи алгоритм сортування, спираючись на тлумачення квантової механіки у кількох світах :
- Перестановка масиву випадковим чином, використовуючи квантовий процес,
- Якщо масив не відсортований, руйнуйте Всесвіт.
- Усі решти всесвіту відсортовані [включаючи той, у якому ви перебуваєте].
(Джерело: http://catb.org/~esr/jargon/html/B/bogo-sort.html )
Зауважте, що великий-O цього алгоритму - O(n)
це будь-який відомий на сьогодні алгоритм сортування на загальних елементах. Коефіцієнт лінійного кроку також дуже низький (оскільки це лише порівняння, а не своп, що робиться лінійно). Подібний алгоритм, власне, може бути використаний для вирішення будь-якої проблеми як NP, так і co-NP у поліноміальний час, оскільки кожне можливе рішення (або можливий доказ відсутності рішення) може бути сформований за допомогою квантового процесу, а потім перевірений у поліномний час.
Однак у більшості випадків ми, мабуть, не хочемо ризикувати, що Кілька Світів можуть бути неправильними, не кажучи вже про те, що акт виконання кроку 2 все ще "залишається як вправа для читача".
У будь-якій точці, коли n обмежений і постійний множник алгоритму O (1) вище, ніж пов'язаний log (n). Наприклад, зберігання значень у хеш-пам'яті є O (1), але може знадобитися дороге обчислення хеш-функції. Якщо елементи даних можна тривіально порівняти (щодо певного порядку), а обмеження на n таке, що log n значно менший, ніж обчислення хешу на будь-якому одному елементі, зберігання в збалансованому бінарному дереві може бути швидшим, ніж зберігання в хештет
У ситуації в реальному часі, коли вам потрібна тверда верхня межа, ви вибрали б, наприклад, велику грудку на відміну від Quicksort, тому що середня поведінка групи gpsort також є її найгіршим поведінкою.
Додаючи до вже хороших відповідей. Практичним прикладом можуть бути індекси Hash vs B-tree індекси в базі даних postgres.
Індекси хеша утворюють індекс хеш-таблиці для доступу до даних на диску, тоді як btree, як випливає з назви, використовує структуру даних Btree.
У часі Big-O це O (1) проти O (logN).
В даний час хеш-індекси відсторонено в постграфах, оскільки в реальній життєвій ситуації, особливо в системах баз даних, домогтися хешування без зіткнення дуже важко (може призвести до найменшої складності O (N)), і через це зробити це ще складніше вони аварійно захищені (називаються записування вперед - WAL в postgres).
Цей компроміс робиться в цій ситуації, оскільки O (logN) досить хороший для індексів, а реалізація O (1) досить складна, і різниця в часі насправді не має значення.
або
Це часто трапляється для застосувань безпеки, які ми хочемо створити проблеми, алгоритми яких цілком повільні, щоб хтось не надто швидко отримав відповідь на проблему.
Ось кілька прикладів з моєї голови.
O(2^n)
де n
знаходиться бітова довжина ключа (це груба сила).В іншому місці CS швидкий сорт - O(n^2)
в гіршому, але в загальному випадку O(n*log(n))
. З цієї причини аналіз "Big O" іноді - не єдине, що вам важливо при аналізі ефективності алгоритму.
O(log n)
алгоритм передO(1)
алгоритмом, якщо розуміють перше, але не останнє ...