Чи є випадки, коли ви віддаєте перевагу більш високий алгоритм складності з великим часом O над нижчим?


242

Чи є випадки, коли ви віддаєте перевагу O(log n)складності O(1)часу перед часовою складністю? Або O(n)до O(log n)?

Чи є у вас приклади?


67
Я б вважав за краще O(log n)алгоритм перед O(1)алгоритмом, якщо розуміють перше, але не останнє ...
Codor

14
Існує багато непрактичних структур даних з операціями O (1) з теоретичної інформатики. Одним із прикладів може бути select () на bitvectors, який може підтримуватися у (n) додатковому просторі та O (1) за операцію, використовуючи 5 шарів непрямості. Простий двійковий пошук у поєднанні з O (1) rank () виявляється більш швидким на практиці, за словами автора бібліотеки «Структурні дані даних»
Нікласа Б.

17
Більш низька асимптотична складність не гарантує швидшого виконання. Дослідження матричного множення на конкретний приклад.
Коннор Кларк

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

19
@Hoten - Це припущення, що пошук таблиці є O (1), що зовсім не є даним для розміру таблиць, про які ви говорите! :)
Джандер

Відповіді:


267

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

  • більшу частину часу більш низьку складність великого типу важче досягти і вимагає кваліфікованої реалізації, багато знань і багато тестування.
  • big-O приховує деталі про константу : алгоритм, який виконує в роботі 10^5, з точки зору big-O кращий, ніж 1/10^5 * log(n)( O(1)vs 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), гіршу, ніж кваксорт або злиття, але як онлайн-алгоритм може ефективно сортувати список значень за їх отриманням (як введення користувача), де більшість інших алгоритмів можуть лише ефективно працювати на повний перелік цінностей.

6
Крім того, я декілька разів бачив, що люди були зосереджені на великому O свого центрального алгоритму, але ігноруючи витрати на налаштування. Наприклад, побудувати хеш-таблицю може бути дорожче, ніж лінійно перебирати масив, якщо вам не потрібно робити це знову і знову. Насправді, завдяки тому, як побудовані сучасні процесори, навіть щось на зразок двійкового пошуку може бути настільки ж швидким на відсортованих масивах, як і лінійний пошук - профілювання є необхідністю.
Луань

@Luaan "Насправді, завдяки тому, як побудовані сучасні процесори, навіть щось на зразок двійкового пошуку може бути таким же швидким на відсортованих масивах, як і лінійний пошук. Профілювання - це необхідність". Цікаво! Чи можете ви пояснити, як двійковий пошук та лінійний пошук можуть зайняти однакову кількість часу на сучасному процесорі?
DJG

3
@Luaan - Неважливо, я знайшов це: schani.wordpress.com/2010/04/30/linear-vs-binary-search
DJG

2
@DenisdeBernardy: Ні, насправді це не так. Вони можуть бути алгоритмами в P. І навіть якби вони не були, під розумними визначеннями того, що означає паралелізація, це не означало б і P! = NP. Також пам’ятайте, що пошук простору можливих пробіг недетермінованої машини тюрінга є досить паралельним.
einpoklum

228

Завжди є прихована константа, яка може бути нижчою за алгоритмом O (log n ). Таким чином, це може працювати швидше на практиці для реальних даних.

Існують також проблеми з космосом (наприклад, біг на тостері).

Існує також проблема часу розробника - O (журнал n ) може бути на 1000 × простішим у впровадженні та перевірці.


Приємно, дякую. Я думав, що може також варто розглянути алгоритм O (logn) для забезпечення стабільності програми (наприклад, у
самоврівноважених

16
Я можу придумати один приклад: для невеликого відсортованого масиву програмісту буде легше і компактніше реалізувати функцію бінарного пошуку, ніж написати повну реалізацію хеш-карти та використовувати її замість цього.
Полковник тридцять два

5
Приклад складності: знайти медіану несортованого списку легко в O (n * log n), але важко зробити в O (n).
Пол Дрейпер

1
-1, не складайте журнали в тостері ... Я жартую, це пляма. lg nце так, так, настільки близько до kвеликих, nщо більшість операцій ніколи не помітить різниці.
corsiKa

3
Існує також той факт, що алгоритмічні складності, з якими знайома більшість людей, не враховують кеш-ефекти. Шукати щось у бінарному дереві є O (log2 (n)) на думку більшості людей, але насправді це набагато гірше, оскільки бінарні дерева мають погану локальність.
Довал

57

Я здивований, що ще ніхто не згадав додатки, пов'язані з пам'яттю.

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

Що я маю на увазі під "обмеженою пам'яттю", це те, що ви часто отримуєте доступ до даних, які постійно перебувають поза кешем. Для того, щоб отримати ці дані, вам доведеться витягнути пам'ять з власне простору пам’яті в кеш, перш ніж ви зможете виконати свою операцію над нею. Цей крок отримання часто буває досить повільним - набагато повільніше, ніж сама ваша операція.

Отже, якщо ваш алгоритм вимагає більше операцій (але ці операції виконуються на даних, які вже є в кеші [і, отже, не потрібно видобувати дані]), він все одно буде виконувати ваш алгоритм з меншою кількістю операцій (які потрібно виконувати на поза Дані кешування [і, отже, потребують отримання]) з точки зору фактичного стінного часу.


1
Алістра вирішила це побічно, розповідаючи про "космічні проблеми"
Зак Сосьє

2
Величезна кількість пропущених кеш-пам'яті лише примножує остаточне виконання на постійне значення (яке не більше 8 для 4-ядерного 3,2 ГГц процесора з частотою 1,6 ГГц, зазвичай це набагато нижче), тому він вважається фіксованою постійною у великій -О нотація. Таким чином, єдине, що викликає помилка кеша, - це переміщення порогу n, де рішення O (n) починає повільніше, ніж рішення O (1).
Маріан Спаник

1
@MarianSpanik Ви, звичайно, правильні. Але це питання , поставлене в ситуації , коли ми вважали за краще б O(logn)більш O(1). Ви можете дуже легко уявити ситуацію, коли за всіх ваших можливих n, менш обмеженою пам’яттю додаток працюватиме в швидшій стіні, навіть при більшій складності.
NoseKnowsВсі

@MarianSpanik не пропускає кеш-пам'ять до 300 тактових циклів? Звідки береться 8?
СподіваюсьДопомога

43

У контекстах, де безпека даних викликає занепокоєння, більш складний алгоритм може бути кращим перед менш складним алгоритмом, якщо складніший алгоритм має кращу стійкість до атак часу .


6
Хоча те, що ви сказали, є істинним, у цьому випадку алгоритм, який виконується в O (1), за визначенням невразливий для атак часу.
Джастін Лессар

17
@JustinLessard: Будучи O (1) означає, що є деякий розмір вводу, після якого час виконання алгоритму обмежується постійною. Що відбувається нижче цього порогу, невідомо. Крім того, поріг може бути навіть не досягнутий для використання в реальному світі алгоритму. Алгоритм може бути лінійним і, таким чином, просочувати інформацію про довжину введення, наприклад.
Йорг W Міттаг

12
Час виконання також може коливатися різними способами, залишаючись при цьому обмеженим. Якщо час виконання пропорційний (n mod 5) + 1, він все O(1)ще розкриває інформацію про n. Отже, більш складний алгоритм із плавнішим часом виконання може бути кращим, навіть якщо він може бути асимптотичним (а можливо, навіть на практиці) повільнішим.
Крістіан Семрау

Це в основному, тому bcrypt вважається хорошим; все робить повільніше
Девід каже: Поновити Моніку

@DavidGrinberg Саме тому bcrypt використовується і відповідає питанню. Але це не пов'язано з цією відповіддю, яка говорить про часові атаки.
Крістіан Семрау

37

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

У вас є список з 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-х прочитаних, і на практиці ви б кешували перше.)


2
Я тут трохи розгублений. Ви говорите про створення масиву з 10 мільярдами (більшість з яких буде невизначено) та трактування UPC як індексу в цей масив?
David Z

7
@DavidZ Так. Якщо ви використовуєте розріджений масив, можливо, ви не отримаєте O (1), але він буде використовувати лише 1 МБ пам'яті. Якщо ви використовуєте фактичний масив, вам гарантується доступ до O (1), але він буде використовувати пам'ять 1/3 ТБ.
Навін

У сучасній системі він використовуватиме 1/3 ТБ адресного простору, але це не означає, що він прийде де-небудь близько до такої виділеної резервної пам'яті. Більшість сучасних ОС не здійснюють зберігання для асигнувань, поки цього не потрібно. Роблячи це, ви, по суті, приховуєте асоціативну структуру пошуку своїх даних всередині ОС віртуальної пам'яті ОС / обладнання.
Філ Міллер

@Novelocrat Щоправда, але якщо ви робите це зі швидкістю оперативної пам’яті, час пошуку не має значення, без причин використовувати 40mb замість 1mb. Версія масиву має сенс лише тоді, коли доступ до пам’яті дорогий - ви переходите на диск.
Лорен Печтел

1
Або коли це не важлива для продуктивності операція, а час розробника дорогий - говорити malloc(search_space_size)та підписуватись на те, що він повертається, так само просто, як це отримується.
Філ Міллер

36

Розгляньте червоно-чорне дерево. Він має доступ, пошук, вставлення та видалення O(log n). Порівняйте з масивом, до якого є доступ, O(1)і решта операцій є O(n).

Отже, враховуючи додаток, де ми вставляємо, видаляємо чи шукаємо частіше, ніж ми отримуємо доступ, і вибір між цими двома структурами, ми віддаємо перевагу червоно-чорному дереву. У цьому випадку ви можете сказати, що ми віддаємо перевагу більш громіздкому O(log n)часу доступу червоно-чорного дерева .

Чому? Тому що доступ не є нашою головною проблемою. Ми робимо компроміс: на ефективність нашого додатку сильніше впливають інші фактори, ніж цей. Ми дозволяємо цьому конкретному алгоритму страждати від продуктивності, оскільки ми отримуємо великі вигоди, оптимізуючи інші алгоритми.

Тож відповідь на ваше запитання полягає в наступному: коли темп зростання алгоритму не те, що ми хочемо оптимізувати , коли ми хочемо оптимізувати щось інше. Усі інші відповіді - це особливі випадки. Іноді ми оптимізуємо час виконання інших операцій. Іноді ми оптимізуємо для пам'яті. Іноді ми оптимізуємо для безпеки. Іноді ми оптимізуємо ремонтопридатність. Іноді ми оптимізуємо час розробки. Навіть переважаюча константа, яка є достатньо низькою для значення, оптимізує час виконання, коли ви знаєте, що швидкість росту алгоритму не має найбільшого впливу на час виконання. (Якщо ваш набір даних знаходився поза цим діапазоном, ви б оптимізували показник зростання алгоритму, оскільки це врешті-решт домінуватиме над постійною.) Все має вартість, і в багатьох випадках ми торгуємо витратами на більш високий темп зростання алгоритм оптимізації чогось іншого.


Не впевнені, як операції, що дозволяють використовувати масив із пошуком O (1) та оновленнями O (n), відповідають червоно-чорному дереву, про що люди думали (принаймні, я). Більшу частину часу я б спочатку замислювався над ключовим пошуком червоно-чорного дерева. Але для збігу з масивом має бути трохи інша структура, яка зберігає кількість підвузлів у верхніх вузлах, щоб забезпечити пошук на основі індексу та повторний індекс при вставці. Хоча я згоден, що для підтримки рівноваги можна використовувати червоно-чорний, але ви можете використовувати збалансоване дерево, якщо хочете бути розпливчастим щодо деталей відповідних операцій.
они

@ony Червоне-чорне дерево можна використовувати для визначення структури типу / карти / словника, але цього не повинно бути. Вузли можуть бути просто елементами, по суті реалізуючи відсортований список.
jpmc26

відсортований список та масив, що визначає порядок елементів, мають різну кількість інформації. Один заснований на порядку між елементами та набором, а інший визначає довільну послідовність, яка не є необхідною, визначає порядок між елементами. Інша річ, що таке "доступ" та "пошук", які ви заявляєте як O(log n)"червоно-чорне дерево"? Вставлення 5в позицію 2 масиву [1, 2, 1, 4]призведе до [1, 2, 5, 1 4](елемент 4отримає індекс оновлений від 3 до 4). Як ви збираєтеся домогтися такої поведінки у O(log n)"червоно-чорному дереві", яке ви називаєте "відсортованим списком"?
ony

@ony "відсортований список та масив, що визначає порядок елементів, мають різну кількість інформації." Так, і це частина того, чому вони мають різні експлуатаційні характеристики. Ви пропускаєте суть. Одне не є краплею заміни іншому у всіх ситуаціях. Вони оптимізують різні речі та роблять різні компроміси , і справа в тому, що розробники постійно приймають рішення щодо цих вигідних заходів.
jpmc26

@ony Доступ, пошук, вставлення та видалення мають конкретні значення в контексті роботи алгоритму. Доступ - це отримання елемента за позицією. Пошук - це розташування елемента за значенням (який має лише будь-яке практичне застосування як перевірку стримування структури, що не має карти). Вставлення та видалення має бути простим. Приклад використання можна побачити тут .
jpmc26

23

Так.

У реальному випадку ми провели кілька тестів на пошук таблиць за допомогою коротких та довгих рядкових клавіш.

Ми використовували х 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) (як хеш-карти) можуть легко мати аналогічний або більший постійний коефіцієнт.


21

Можливість паралельного виконання алгоритму.

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

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


Але все, що робить паралелізація - це зменшити постійний фактор, про який говорили інші, правда?
gengkev

1
Так, але паралельний алгоритм може ділити постійний коефіцієнт на 2 щоразу, коли ви подвоюєте кількість виконавчих машин. Інший єдиний алгоритм з потоком може зменшити постійний коефіцієнт лише один раз постійним чином. Тож за допомогою паралельного алгоритму ви можете динамічно реагувати на розмір n та бути швидшими за час виконання настінного годинника.
Симулятор

15

Скажімо, ви впроваджуєте чорний список у вбудованій системі, де цифри від 0 до 1 000 000 можуть бути в чорному списку. Це залишає вам два можливі варіанти:

  1. Використовуйте біт в 1000000 біт
  2. Використовуйте відсортований масив цілих чисел у чорному списку та використовуйте двійковий пошук для доступу до них

Доступ до біт-набору матиме гарантований постійний доступ. З точки зору складності часу, це оптимально. Як з теоретичної, так і з практичної точки зору (це O (1) з надзвичайно низькою постійною накладними витратами).

Тим не менш, ви можете скористатися другим рішенням. Особливо, якщо ви очікуєте, що кількість цілих чисел у чорному списку буде дуже малою, оскільки це буде більш ефективною пам'яттю.

І навіть якщо ви не розробляєте для вбудованої системи, де пам’яті мало, я просто можу збільшити довільну межу від 1 000 000 до 1 000 000 000 000 і зробити той же аргумент. Тоді для бітсету було б потрібно близько 125 Г пам'яті. Маючи гарантовану найгіршу складність O (1), можливо, не переконати вашого начальника надати вам такий потужний сервер.

Тут я б наголосив на двійковому пошуку (O (log n)) або бінарному дереві (O (log n)) над бітом O (1). І, ймовірно, хеш-таблиця зі своєю найгіршою складністю O (n) обіграє їх усіх на практиці.



12

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

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

Поширені приклади: хештелі, розширення "списків масивів" (також масиви / вектори, що мають динамічний розмір).

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


11

Більш загальне питання, якщо є ситуації , в яких один волів би 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 років алгоритм симплекс все ще здається алгоритмом вибору (за винятком певних дуже великих проблем). Це з очевидної причини, що поведінка в середньому випадку часто важливіша, ніж поведінка в гіршому випадку, а також з більш тонкої причини, що алгоритм симплексу в деякому сенсі є більш інформативним (наприклад, інформацію про чутливість легше витягти).


10

Щоб покласти мої 2 копійки в:

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

У цьому випадку алгоритм O (logn) (припустимо, він отримує доступ до диска послідовно), стає більш сприятливим.


Я можу додати тут, що на накопичувачі або стрічці з послідовним доступом алгоритм O (1) натомість стає O (n), тому послідовне рішення стає більш сприятливим. Багато операцій O (1) залежать від того, що додавання та індексований пошук є алгоритмом постійного часу, якого він не знаходиться в просторі послідовного доступу.
TheHansans

9

Існує хороший випадок використання алгоритму O (log (n)) замість алгоритму O (1), який численні інші відповіді ігнорували: незмінність. Хеш-карти мають O (1) ставить і отримує, припускаючи хороший розподіл хеш-значень, але вони вимагають змінного стану. На незмінних картах дерев O (log (n)) ставить і отримує, що асимптотично повільніше. Однак незмінність може бути досить цінною, щоб компенсувати більш низьку продуктивність, і у випадку, коли потрібно зберегти кілька версій карти, незмінність дозволяє уникнути необхідності копіювання карти, яка є O (n), і, отже, може покращитись виконання.


9

Просто: Тому що коефіцієнт - витрати, пов’язані з налаштуванням, зберіганням та часом виконання цього кроку - може бути значно, значно більшим із меншою проблемою big-O, ніж з більшою. Big-O - це лише міра масштабованості алгоритмів .

Розглянемо наступний приклад із словника Хекера, пропонуючи алгоритм сортування, спираючись на тлумачення квантової механіки у кількох світах :

  1. Перестановка масиву випадковим чином, використовуючи квантовий процес,
  2. Якщо масив не відсортований, руйнуйте Всесвіт.
  3. Усі решти всесвіту відсортовані [включаючи той, у якому ви перебуваєте].

(Джерело: http://catb.org/~esr/jargon/html/B/bogo-sort.html )

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

Однак у більшості випадків ми, мабуть, не хочемо ризикувати, що Кілька Світів можуть бути неправильними, не кажучи вже про те, що акт виконання кроку 2 все ще "залишається як вправа для читача".


7

У будь-якій точці, коли n обмежений і постійний множник алгоритму O (1) вище, ніж пов'язаний log (n). Наприклад, зберігання значень у хеш-пам'яті є O (1), але може знадобитися дороге обчислення хеш-функції. Якщо елементи даних можна тривіально порівняти (щодо певного порядку), а обмеження на n таке, що log n значно менший, ніж обчислення хешу на будь-якому одному елементі, зберігання в збалансованому бінарному дереві може бути швидшим, ніж зберігання в хештет


6

У ситуації в реальному часі, коли вам потрібна тверда верхня межа, ви вибрали б, наприклад, велику грудку на відміну від Quicksort, тому що середня поведінка групи gpsort також є її найгіршим поведінкою.


6

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

Індекси хеша утворюють індекс хеш-таблиці для доступу до даних на диску, тоді як btree, як випливає з назви, використовує структуру даних Btree.

У часі Big-O це O (1) проти O (logN).

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

Цей компроміс робиться в цій ситуації, оскільки O (logN) досить хороший для індексів, а реалізація O (1) досить складна, і різниця в часі насправді не має значення.



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

або

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

3
  1. при перепроектуванні програми виявляється, що процедура оптимізована за допомогою O (1) замість O (lgN), але якщо це не вузьке місце цієї програми, і важко зрозуміти альг O (1). Тоді вам не доведеться використовувати алгоритм O (1)
  2. коли O (1) потрібно багато пам'яті, яку ви не можете поставити, тоді як час O (lgN) можна прийняти.

1

Це часто трапляється для застосувань безпеки, які ми хочемо створити проблеми, алгоритми яких цілком повільні, щоб хтось не надто швидко отримав відповідь на проблему.

Ось кілька прикладів з моєї голови.

  • Хешування паролів іноді робиться довільно повільним, щоб ускладнити відгадування паролів грубою силою. Цей пост з питань інформаційної безпеки містить позначку про це (і багато іншого).
  • Бітова монета використовує керовану повільну проблему для мережі комп’ютерів для вирішення з метою «видобутку» монет. Це дозволяє колективній системі видобувати валюту за контрольованим курсом.
  • Асиметричні шифри (як RSA ) призначені для дешифрування без ключів, навмисно повільних, щоб не допустити, щоб хтось без приватного ключа зламав шифрування. Алгоритми розроблені для того, щоб розбитись, сподіваючись, O(2^n)де nзнаходиться бітова довжина ключа (це груба сила).

В іншому місці CS швидкий сорт - O(n^2)в гіршому, але в загальному випадку O(n*log(n)). З цієї причини аналіз "Big O" іноді - не єдине, що вам важливо при аналізі ефективності алгоритму.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.