"Для малих значень n, O (n) можна трактувати так, ніби це O (1)"


26

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

Приклад :

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

Що достатньо мало? 10? 100? 1000? У який момент ви говорите "ми більше не можемо ставитися до цього, як до безкоштовної операції"? Чи є правило:

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


4
Правило роботи залежить від того, яку проблему ви хочете вирішити. Будьте швидкими у вбудованих системах з n100 ? Опублікувати в теорії складності?
Рафаель

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

12
@rianjs Ви , здається, помилково приймаючи O(1)за безкоштовно . Міркування перших кількох речень - O(1)це постійність , яка іноді може бути шалено повільною. Розрахунок, який займає тисячу мільярдів років незалежно від вкладу, - це O(1)розрахунок.
Mooing Duck

1
Пов'язане питання про те, чому ми в першу чергу використовуємо асимптотику.
Рафаель

3
@rianjs: пам’ятайте про жарти по лінії «п’ятикутник - це приблизно коло, для досить великих значень 5». Присуд, про який ви питаєте, має сенс, але оскільки він викликав у вас певну плутанину, можливо, варто поцікавитись Еріком Ліппертом, наскільки цей точний вибір фразування мав гумористичний ефект. Він міг би сказати, "якщо на є будь-яка верхня межа, то кожна проблема є O ( 1 ) " і все-таки була математично правильною. "Малий" не є частиною математики. nO(1)
Стів Джессоп

Відповіді:


21

Всі порядки включають постійну , фактично декілька з них. Коли кількість предметів досить велика, ця константа не має значення. Питання в тому, чи кількість предметів є достатньо малою, щоб ця константа домінувала.C

Ось наочний спосіб подумати про це.

введіть тут опис зображення

Усі мають постійну старт, яка визначає їх початкову точку на осі Y. Кожен також має критичну константу домінує, як швидко вони зростатимуть.C

  • Для , С визначає час.O(1)C
  • дійсно C × n , де C визначає кут.O(n)C×nC
  • - це дійсно ( C × n ) 2 , де C визначає різкість кривої.O(n2)(C×n)2C

Щоб визначити, який алгоритм слід використовувати, потрібно оцінити місце, де перетинаються періоди виконання. Наприклад, рішення з високим часом запуску або високим C втрачає рішення O ( n ) з низьким часом запуску і низьким C при досить великій кількості елементів.O(1)CO(n)C

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

Ось приклад CS. Скажімо, вам потрібен список, який завжди сортується. Ви можете використовувати дерево, яке збереже себе в порядку для . Або ви можете використовувати несортований список і пересортувати після кожного вставки або видалення в O ( n log n ) . Оскільки операції з деревом є складними (у них висока константа), а сортування настільки просте (низька константа), список, ймовірно, виграє сотні чи тисячі елементів.O(logn)O(nlogn)

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

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


Безглуздо говорити . Те , що ви на самому ділі означає, що якщо час роботи становить T = O ( 1 ) , то (у багатьох випадках) T C . Якщо T = O ( n ), то у багатьох випадках T C n , або більш формально T = C n + o ( n ) . І так далі. Однак зауважте, що в інших випадках константа C змінюється в залежності відO(1)=O(C)T=O(1)TCT=O(n)TCnT=Cn+o(n)C , у певних межах. n
Yuval Filmus

@YuvalFilmus Ось чому мені подобаються графіки.
Шверн

Це найкраща відповідь на сьогодні, справа в тому, як швидко розвивається функція.
Рікардо

1
Хороший графік, але ось дійсно слід позначати "часом", а не "швидкістю". y
Ільмарі Каронен

1
Чи дійсно лінія парабола? Він виглядає дуже плоским для малого n і дуже крутим для великого n . O(n2)nn
Девід Річербі

44

Це значною мірою підтримує скарги на вже опубліковані відповіді, але може запропонувати іншу точку зору.

Показово, що питання обговорює "досить малі значення n ". Вся суть Big-O полягає в тому, щоб описати, як обробка зростає як функція того, що обробляється. Якщо дані, що обробляються, залишаються невеликими, неважливо обговорювати Big-O, тому що вас не цікавить зростання (що не відбувається).

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

Для малих росіян використовуйте все, що зручно.

Якщо ви їдете в поїздку по всій країні, то вам потрібно розглянути шляхи оптимізації їзди, пробіг газу тощо.


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

4
Чудова метафора!
Еворлор

1
З чисто математичної точки зору, асимптотична складність нічого не говорить про те, коли n < infinity.
Гордон Густафсон

15

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

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

Більш практичне справу, що, коли ми говоримо , що час роботи алгоритму є , це означає тільки те , що вона асимптотично з п 2  кроку, для деякої константи  C . Тобто є деяка константа n 0 така, що для всіх n n 0 алгоритм виконує приблизно c n 2 кроки. Але може бути n 0 = 100 , 000 , 000Θ(n2) cn2Cn0nn0cn2n0=100,000,000і вас цікавлять лише екземпляри, розміри яких набагато менші. Асимптотична квадратична межа може навіть не застосовуватися до ваших невеликих примірників. Можливо, вам пощастить, і на невеликих входах це може бути швидше (або ви можете не пощастити і повільніше). Наприклад, для малих  , n 2 < 1000 n, тому ви краще запустите квадратичний алгоритм з хорошими константами, ніж лінійний алгоритм з поганими константами. Прикладом реального життя є те, що асимптотично найефективніші алгоритми множення матриці (варіанти Копперсміта – Винограда , що працюють в часі O ( n 2.3729 ) ) рідко використовуються на практиці, оскільки О Strassen'snn2<1000nO(n2.3729) алгоритм швидший, якщо ваші матриці дійсно великі.O(n2.8074)

Третій момент полягає в тому, що якщо  малий, n 2 і навіть n 3  малі. Наприклад, якщо вам потрібно сортувати кілька тисяч елементів даних і вам потрібно сортувати їх лише один раз, будь-який алгоритм сортування досить хороший: a Θ ( n 2 )nn2n3Θ(n2)Алгоритм все ще потребує, можливо, декількох десятків мільйонів інструкцій для сортування ваших даних, що зовсім не так багато часу на процесорі, який може виконувати мільярди інструкцій в секунду. Гаразд, також є доступ до пам'яті, але навіть повільний алгоритм займе менше секунди, тому, мабуть, краще скористатися простим, повільним алгоритмом і правильно його, ніж використовувати складний, швидкий алгоритм і виявити, що це блискавично але баггі і насправді не сортує дані належним чином.


4
Незважаючи на цілком правильні та дійсні бали, я думаю, що ви пропустили бал. Здається, вони означали, що іноді алгоритм з працює краще, ніж алгоритм з O ( 1 ) , для досить малих n s. Це відбувається, наприклад, коли перший має час роботи 10 n + 50 , тоді як останній працює в 100000 . Тоді для n досить малих фактично швидше використовувати O ( n ) протокол. O(n)O(1)n10n+50100000nO(n)
Ран Г.

@RanG. Чи це не підпадає під мій другий випадок? (Особливо, якщо я редагую це, щоб сказати щось на кшталт "Лінійний алгоритм з хорошими константами може бити константний / логарифмічний алгоритм з поганими константами"?)
Девід Річербі

1
Добре було б чітко згадати важливість констант, коли n мало. Це, мабуть, не траплялося б тому, хто цього не чув.
Роб Уоттс

9

Нотація Big-O насправді лише щось говорить про поведінку для довільно великих n. Наприклад, означає, що існує константа c> 0 і ціле число n 0 таке, що f ( n ) < c n 2 для кожного n > n 0 .f(n)=O(n2)n0f(n)<cn2n>n0

У багатьох випадках можна знайти константу c і сказати "Для кожного n> 0, f (n) - приблизно ". Яка корисна інформація мати. Але в деяких випадках це неправда. Якщо f (n) = n 2 + 10 18 , то це цілком оману. Так що лише те, що щось є O (n ^ 2), не означає, що ви можете вимкнути мозок і проігнорувати фактичну функцію.cn2n2+1018

З іншого боку, якщо ви коли-небудь стикаєтесь зі значеннями n = 1, 2 і 3, то на практиці це не має значення, що f (n) робить для n ≥ 4, тож ви можете також вважати, що f ( n) = O (1), з c = max (f (1), f (2), f (3)). І ось що означає досить малий: Якщо твердження, що f (n) = O (1) не вводить вас в оману, якщо єдині значення f (n), з якими ви стикаєтесь, "достатньо малі".


5

Якщо він не росте, це O (1)

Заява автора трохи аксіоматична.

Замовлення зростання описують те, що відбувається з кількістю роботи, яку ви повинні виконати, як Nзбільшується. Якщо ви знаєте, що Nце не збільшується, ваша проблема ефективно O(1).

Пам'ятайте, що O(1)це не означає "швидкий". Алгоритм, для виконання якого завжди потрібно 1 трлн кроків, є O(1). Алгоритм, який займає від 1-200 кроків, але ніколи більше, є O(1). [1]

Якщо ваш алгоритм виконує саме такі N ^ 3кроки, і ви знаєте, що Nне може бути більше 5, він ніколи не може зробити більше 125 кроків, тому це ефективно O(1).

Але знову ж таки, O(1)не обов'язково означає «досить швидко». Це окреме питання, яке залежить від вашого контексту. Якщо на те, щоб закінчити щось, потрібен тиждень, вам, мабуть, все одно, якщо це технічно O(1).


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


1
Це все звучить дійсно, за винятком цього: "Якщо ваш алгоритм робить рівно N ^ 3 кроки, і ви знаєте, що N не може бути більше 5, він ніколи не може зробити більше 125 кроків, значить, це O (1)." . Знову ж таки, якщо алгоритм бере ціле число, а моя максимальна ціла підтримка становить 32767, це O (1)? Очевидно, що ні. Big-O не змінюється на основі обмежень параметрів. Це O (n), навіть якщо ви знаєте, що 0 <n <3, оскільки n = 2 займає вдвічі більше, ніж n = 1.
JSobell

3
@JSobell Але це O (1). Якщо є обмеження, яке обмежує ваш n для f (n), це означає, що воно не може рости нескінченно. Якщо ваш n обмежений 2 ^ 15, ваша велика n ^ 2 функція є насправді g(n) = min(f(2^15), f(n))- що є в O (1). Зазначене на практиці константи мають велике значення і, очевидно, n може стати досить великим, щоб асимптотичний аналіз був корисним.
Во

2
@JSobell Це схоже на питання про те, чи справді комп'ютери є "Turing Complete", враховуючи, що технічно вони не можуть мати нескінченний простір для зберігання. Технічно математично комп'ютер - це не "справжня" машина Тьюрінга. На практиці не існує такого поняття, як "нескінченна стрічка", але жорсткі диски наближаються досить близько.
Кайл Странд

Я писав систему фінансових ризиків кілька років тому, яка включала маніпуляції з матрицею n ^ 5, тому мала практичну межу n = 20, перш ніж ресурси стали проблемою.
JSobell

Вибачте, натисніть Enter занадто рано. Я писав систему фінансових ризиків кілька років тому, яка включала маніпуляції з матрицею n ^ 5, тому мала практичну межу n = 20, перш ніж ресурси стали проблемою. Відповідно до цієї хибної логіки, створена функція - O (1), тому що у мене обмеження 20. Коли клієнт каже "Хм, можливо, ми повинні перемістити її до 40 як граничну ... Так, алгоритм - O (1 ), тому це не проблема "... Ось чому межі на вході безглузді. Функція була O (n ^ 5), а не O (1), і це практичний приклад того, чому Big-O не залежить від меж.
JSobell

2

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

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


Якщо ви хочете бути впевнені, зробіть кілька експериментів, щоб побачити, яка структура даних найкраща для ваших параметрів.
Yuval Filmus

@Telastyn Yuval Filmus має рацію, якщо ти справді хочеш бути впевненим. Я знаю людину на ім'я Джим, його параметри в порядку. Але він не прислухався до таких порад Юваля. Вам слід справді слухати Юваль, щоб бути впевненим і безпечним.
ПоінформувалиA

2

Хоча цитата справжня (але розпливчаста), для неї є і небезпеки. Отже, ви повинні розглядати складність на будь-якій стадії вашої заявки.

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

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

(майте на увазі, це все ще невеликий сценарій списку.)

Через 3 роки я приїхав, і мій бос щойно здійснив великий продаж: наше програмне забезпечення буде використовуватися великим національним роздрібним торговцем. До цього ми лише обслуговували невеликі магазини. І тепер мій начальник на мене присягає і кричить, чому програмне забезпечення, яке завжди «добре працювало», зараз так страшно повільне.

Виявляється, цей список був списком клієнтів, а наших клієнтів було лише приблизно 100 клієнтів, тому ніхто не помічав. Операція заповнення списку в основному була операцією O (1), оскільки вона займала менше мілісекунди. Ну, не так вже й багато, коли до нього потрібно додати 10 000 клієнтів.

І через роки після первісного поганого рішення O (1) компанія майже втратила великого клієнта. Все через одну маленьку помилку дизайну / припущення років тому.


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

1

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

Якщо у мене є два алгоритми з цими часами:

  • журнал (n) +10000
  • n + 1

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

Якщо n залишається малим, то кожна проблема - O (1)!

Я спекулюють , що тут це означає, що якщо nобмежена, то кожна проблема є O (1). Наприклад, якщо ми сортуємо цілі числа, ми можемо вирішити використовувати quicksort. O(n*log(n))очевидно. Але якщо ми вирішимо, що ніколи не може бути більше 2^64=1.8446744e+19цілих чисел, знаємо, що n*log(n)<= 1.8446744e+19*log(1.8446744e+19)<= 1.1805916e+21. Тому алгоритм завжди займе менше 1.1805916e+21"одиниць часу". Оскільки це постійний час, ми можемо сказати, що алгоритм завжди можна виконати в той постійний час -> O(1). (Зауважте, що навіть якщо ці одиниці часу є наносекундами, це загальна сума понад 37411 років). Але все ж O(1).


0

Я підозрюю, що багато з цих відповідей не мають фундаментальної концепції. O (1): O (n) - не те саме, що f (1): f (n), де f - одна і та ж функція, оскільки O не являє собою жодної функції. Навіть хороший графік Шверна недійсний, оскільки він має однакову вісь Y для всіх рядків. Щоб усі користувались однією віссю, лінії повинні бути fn1, fn2 та fn3, де кожен був функцією, продуктивність якої можна було б безпосередньо порівняти з іншими.

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

Що ж, якщо n = 1 - вони точно однакові? Ні. Функція, що дозволяє змінювати кількість ітерацій, не має нічого спільного з тією, яка не має, позначення big-O не хвилює, і ми також не повинні.

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

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

Ось подібне запитання: Якщо я перебираю рядок символів і знаю, що в цілому мої рядки будуть менше 10 символів, чи можу я сказати, що це еквівалент O (1), але якщо мої рядки були довші, то я скажу, що це був О (п)?

Ні, тому що рядок з 10 символів займає в 10 разів більше, ніж рядок з 1 символом, але в 100 разів менше, ніж рядок з 1000 символів! Це на).


О(1)f(i)iмакс{f(0),,f(10)}О(1)

Так, і це приклад, коли нотація Big-O зазвичай не розуміється. Згідно з вашим аргументом, якщо я знаю, що максимальне значення n - 1 000 000, то моя функція - O (1). Насправді моя функція могла б бути в кращому випадку O (1) і в гіршому O (n). Ця позначення використовується для опису алгоритмічної складності, а не конкретної реалізації, і ми завжди використовуємо найдорожчий для опису сценарію, а не найкращий. Насправді, за вашим аргументом, кожна функція, яка дозволяє n <2, є O (1)! :)
JSobell

n<2O(1)f(n)f(10)нО(1)

Вибачте, але якщо ви говорите, що пізнання верхніх меж n робить функцію O (1), то ви говорите, що нотаційне представлення безпосередньо пов'язане зі значенням n, і це не так. Все інше, що ви згадуєте, є правильним, але припускаючи, що оскільки n має межі, це O (1) не є правильним. На практиці є місця, де те, що ви описуєте, може спостерігатися, але ми дивимось тут на позначення Big-O, а не на функціональне кодування. Отже, чому б ви припустили, що n, маючи максимум 10, зробить це O (1)? Чому 10? Чому б не 65535, або 2 ^ 64?
JSobell

Сказавши, що якщо ви пишете функцію, яка прошиває рядок до 10 символів, то завжди петлю над рядком, то це O (1), тому що n завжди 10 :)
JSobell

0

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

О(1)О(1)

Тепер візьмемо порівняно невеликий набір з 10 елементів і матимемо декілька алгоритмів для їх сортування (лише приклад). Припустимо, що ми зберігаємо елементи в структурі, яка також надає нам алгоритм, здатний сортувати елементи в постійному часі. Скажімо, наші алгоритми сортування можуть мати такі складності (із позначенням big-O):

  1. О(1)
  2. О(н)
  3. О(нлог(н))
  4. О(н2)

О(1)

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

  1. 200
  2. 11н
  3. 4нлог(н)
  4. 1н2

Якщо наше введення має розмір 10, то це точна кількість кроків для кожного алгоритму, згаданого вище:

  1. 200
  2. 11×10=110
  3. 4×10×3.32134
  4. 1×100=100

О(н2)О(1),О(н)О(нлог(н))О(н2)О(1)О(н2)О(1) через приховану константу. І якщо константа відносно велика в порівнянні з розміром вводу, це може мати значення більше, ніж асимптотична складність.

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