Я вважаю за краще якомога менше формального визначення та простої математики.
Я вважаю за краще якомога менше формального визначення та простої математики.
Відповіді:
Швидке зауваження, це майже напевно плутає нотацію Big O (яка є верхньою межею) з позначенням Theta "Θ" (що є двостороннім). На мій досвід, це насправді характерно для дискусій у неакадемічних умовах. Вибачте за будь-яку причину плутанини.
Велику складність O можна візуалізувати за допомогою цього графіка:
Найпростіше визначення, яке я можу дати для позначення Big-O, це:
Нотація Big-O - це відносне подання складності алгоритму.
У цьому реченні є кілька важливих і свідомо вибраних слів:
- відносно: ви можете порівняти лише яблука з яблуками. Ви не можете порівняти алгоритм для арифметичного множення з алгоритмом, який сортує список цілих чисел. Але порівняння двох алгоритмів виконання арифметичних операцій (одне множення, одне додавання) підкаже вам щось значуще;
- представлення: Big-O (у найпростішому вигляді) зменшує порівняння між алгоритмами до однієї змінної. Ця змінна вибирається на основі спостережень чи припущень. Наприклад, алгоритми сортування зазвичай порівнюються на основі операцій порівняння (порівняння двох вузлів для визначення їх відносного впорядкування). Це передбачає, що порівняння є дорогим. Але що робити, якщо порівняння дешеве, але заміна дорога? Це змінює порівняння; і
- складність: якщо для сортування 10000 елементів мені знадобиться одна секунда, скільки часу мені знадобиться, щоб сортувати мільйон? Складність у цьому випадку є відносною мірою до чогось іншого.
Поверніться і перечитайте вищесказане, коли прочитаєте решту.
Найкращий приклад Big-O, який я можу придумати, - це робити арифметику. Візьміть два числа (123456 і 789012). Основними арифметичними операціями, які ми вивчили в школі, були:
- додавання;
- віднімання;
- множення; і
- поділ.
Кожне з них - це операція чи проблема. Метод їх вирішення називається алгоритмом .
Доповнення є найпростішим. Ви підкреслюєте числа вгору (праворуч) і додаєте цифри в стовпчик, в якому записується останнє число цього додавання. Частина «десятки» цього числа переноситься до наступного стовпця.
Припустимо, що додавання цих чисел є найдорожчою операцією в цьому алгоритмі. Цілком очевидно, що для того, щоб додати ці два числа разом, ми повинні додати разом 6 цифр (і, можливо, мати 7). Якщо ми додамо два 100-значні числа разом, ми повинні зробити 100 доповнень. Якщо ми додамо два 10 000-значні числа, ми повинні зробити 10 000 доповнень.
Бачите візерунок? Складність (будучи кількість операцій) прямо пропорційна числу цифр п в більшій кількості. Ми називаємо це O (n) або лінійною складністю .
Віднімання схоже (крім того, що вам може знадобитися запозичити замість того, щоб носити).
Множення різне. Ви підкреслюєте числа вгору, беруть першу цифру в нижній номер і помножують її по черзі на кожну цифру в верхньому номері і так далі через кожну цифру. Отже, для множення наших шестизначних чисел ми повинні зробити 36 множення. Для отримання кінцевого результату нам може знадобитися додати 10 або 11 стовпців.
Якщо у нас є два 100-значні числа, нам потрібно зробити 10000 множення і 200 додавань. Для двох одномільйонних чисел нам потрібно зробити множення на трильйон (10 12 ) та два мільйони додавань.
Оскільки алгоритм масштабується з n- квадратом , це O (n 2 ) або квадратична складність . Настав час для впровадження ще однієї важливої концепції:
Ми дбаємо лише про найбільш значну частину складності.
Проникливий, можливо, зрозумів, що ми можемо виразити кількість операцій як: n 2 + 2n. Але, як ви бачили з нашого прикладу з двома цифрами в мільйон цифр за штуку, другий доданок (2n) стає незначним (складає 0,0002% від загальної кількості операцій до цього етапу).
Можна помітити, що ми тут припустили найгірший сценарій. При множенні шестизначних чисел, якщо одна з них має 4 цифри, а інша - 6 цифр, ми маємо лише 24 множення. І все-таки ми обчислюємо найгірший сценарій для цього "n", тобто коли обидва - це 6-значне число. Отже, позначення Big-O стосуються найгіршого сценарію алгоритму.
Наступний найкращий приклад, який я можу придумати, - це телефонна книга, яку зазвичай називають Білими Сторінками або подібною, але вона залежить від країни до країни. Але я говорю про ту, яка перераховує людей за прізвищем, а потім ініціалами чи іменем, можливо, адресою, а потім номерами телефонів.
Тепер, якби ви доручили комп’ютеру шукати номер телефону "Джон Сміт" у телефонній книжці, яка містить 1 000 000 імен, що б ви зробили? Ігноруючи той факт, що ви могли здогадатися, наскільки далеко в S почалося (припустимо, ви не можете), що б ви зробили?
Типовою реалізацією може бути відкриття до середини, взяття 500 000- го і порівняння зі "Смітом". Якщо трапиться "Сміт, Джон", нам просто пощастило. Набагато ймовірніше, що "Джон Сміт" буде до цього імені або після нього. Якщо це після, ми розділимо останню половину телефонної книги навпіл і повторимо. Якщо це раніше, то ділимо першу половину телефонної книги навпіл і повторюємо. І так далі.
Це називається двійковим пошуком і використовується щодня в програмуванні, усвідомлюєте ви це чи ні.
Тож якщо ви хочете знайти ім’я в телефонній книзі на мільйон імен, ви можете фактично знайти будь-яке ім’я, зробивши це не більше 20 разів. Порівнюючи алгоритми пошуку, ми вирішуємо, що це порівняння - це наше «n».
- Для телефонної книги з 3 імен потрібно 2 порівняння (максимум).
- Для 7 потрібно максимум 3.
- Для 15 потрібно 4.
- …
- На 1 000 000 потрібно 20.
Це надзвичайно добре, чи не так?
У термінах Big-O це O (log n) або логарифмічна складність . Тепер розглянутий логарифм може бути ln (основа e), log 10 , log 2 або якась інша база. Не має значення, що все ще O (log n) так само, як O (2n 2 ) і O (100n 2 ), як і раніше O (n 2 ).
На цьому етапі варто пояснити, що Big O можна використовувати для визначення трьох випадків за допомогою алгоритму:
- Найкращий випадок: У пошуку телефонної книги найкращим випадком є те, що ми знайдемо ім’я в одному порівнянні. Це O (1) або постійна складність ;
- Очікуваний випадок: Як обговорювалося вище, це O (log n); і
- Найгірший випадок: це також O (log n).
Зазвичай нас не хвилює найкраща справа. Нас цікавить очікуваний і найгірший випадок. Іноді те чи інше з них буде важливішим.
Повернутися до телефонної книги.
Що робити, якщо у вас є номер телефону і хочете знайти ім’я? У поліції є зворотна телефонна книга, але такі огляди відмовляються широкій громадськості. Або вони? Технічно ви можете скасувати номер пошуку у звичайній телефонній книзі. Як?
Ви починаєте з імені та порівнюєте число. Якщо це матч, чудово, якщо ні, ви переходите до наступного. Ви повинні зробити це так, оскільки телефонна книга не упорядкована (все одно за номером телефону).
Отже, щоб знайти ім'я за номером телефону (зворотний пошук):
- Кращий випадок: O (1);
- Очікуваний випадок: O (n) (для 500 000); і
- Найгірший випадок: O (n) (на 1 000 000).
Це досить відома проблема інформатики і заслуговує на згадку. У цій проблемі у вас є N міст. Кожне з цих міст пов'язане з одним або кількома іншими містами дорогою на певній відстані. Проблема продавця подорожей полягає в тому, щоб знайти найкоротший тур, який відвідує кожне місто.
Звучить просто? Подумати ще раз.
Якщо у вас є три міста A, B і C з дорогами між усіма парами, ви можете поїхати:
- A → B → C
- A → C → B
- B → C → A
- B → A → C
- C → A → B
- C → B → A
Ну, насправді їх менше, тому що деякі з них еквівалентні (A → B → C і C → B → A еквівалентні, наприклад, тому що вони використовують однакові дороги, просто навпаки).
Насправді є 3 можливості.
- Візьміть це в 4 міста, і у вас є (iirc) 12 можливостей.
- З 5 це 60.
- 6 стає 360.
Це функція математичної операції, яка називається факторіальною . В основному:
- 5! = 5 × 4 × 3 × 2 × 1 = 120
- 6! = 6 × 5 × 4 × 3 × 2 × 1 = 720
- 7! = 7 × 6 × 5 × 4 × 3 × 2 × 1 = 5040
- …
- 25! = 25 × 24 ×… × 2 × 1 = 15,511,210,043,330,985,984,000,000
- …
- 50! = 50 × 49 ×… × 2 × 1 = 3.04140932 × 10 64
Отже проблема Big-O продавця подорожей - це O (n!) Або факторна чи комбінаторна складність .
До того часу, як ви потрапите до 200 міст, у Всесвіті залишилось недостатньо часу для вирішення проблеми з традиційними комп’ютерами.
Щось подумати.
Ще один момент, про який я хотів би швидко згадати, - це те, що будь-який алгоритм, що має складність O (n a ) , має складність у поліномі або є розв'язним у поліноміальний час .
O (n), O (n 2 ) тощо - це весь час многочлена. Деякі проблеми неможливо вирішити за багаточлен. Певні речі використовуються у світі через це. Криптографія відкритого ключа - яскравий приклад. Обчислювально важко знайти два найважливіші фактори дуже великої кількості. Якби це не так, ми не могли б використовувати системи відкритих ключів, які ми використовуємо.
У всякому разі, це все для мого (сподіваюсь простого англійського) пояснення Big O (переглянутого).
Він показує, як алгоритм масштабується на основі розміру вводу.
O (n 2 ) : відомий як квадратична складність
Зверніть увагу , що кількість елементів зростає в 10 разів , але час збільшується на коефіцієнт 10 2 . В основному, n = 10 і так O (n 2 ) дає нам коефіцієнт масштабування n 2, який дорівнює 10 2 .
O (n) : відома як лінійна складність
Цього разу кількість предметів збільшується в 10 разів, а також час. n = 10 і тому коефіцієнт масштабування O (n) дорівнює 10.
O (1) : відомий як Постійна складність
Кількість предметів як і раніше збільшується в 10 разів, але коефіцієнт масштабування O (1) завжди дорівнює 1.
O (log n) : відомий як логарифмічна складність
Кількість обчислень збільшується лише журналом вхідного значення. Тож у цьому випадку, якщо припустити, що кожне обчислення займає 1 секунду, журнал введення n
- це необхідний час log n
.
У цьому суть. Вони зменшують математику вниз, щоб це не було точно n 2 або що б там не говорили, але це буде домінуючим фактором масштабування.
Нотація Big-O (також її називають нотацією "асимптотичний ріст") - це те, що функції "виглядають", якщо ви ігноруєте постійні фактори та інше, що знаходиться поблизу походження . Ми використовуємо це, щоб поговорити про те, як річ масштабується .
Основи
для "досить" великих входів ...
f(x) ∈ O(upperbound)
означає f
"росте не швидше"upperbound
f(x) ∈ Ɵ(justlikethis)
означає f
"росте точно так само"justlikethis
f(x) ∈ Ω(lowerbound)
означає f
"росте не повільніше"lowerbound
Позначення big-O не переймаються постійними факторами: функція, 9x²
як кажуть, "росте точно так само" 10x²
. Ні асимптотичні позначення великого рівня O не дбають про неасимптотичні речі ("речі біля походження" або "що відбувається, коли розмір проблеми невеликий"): функція, 10x²
як кажуть, "росте точно так само" 10x² - x + 2
.
Чому ви хочете ігнорувати менші частини рівняння? Тому що вони стають абсолютно ослабленими великими частинами рівняння, якщо ви вважаєте все більші та більші масштаби; їх внесок стає карликовим і неактуальним. (Див. Приклад розділу.)
По-іншому, це все в співвідношенні, як ви переходите до нескінченності. Якщо поділити фактичний час, який він займає O(...)
, ви отримаєте постійний коефіцієнт в межах великих входів. В інтуїтивному сенсі це має сенс: функції "масштабуються як" одна за одною, якщо ви можете помножити одну, щоб отримати іншу. Це коли ми говоримо ...
actualAlgorithmTime(N) ∈ O(bound(N))
e.g. "time to mergesort N elements
is O(N log(N))"
... це означає, що для "досить великих" проблемних розмірів N (якщо ми ігноруємо матеріали, що знаходяться біля джерела), існує деяка константа (наприклад, 2,5, повністю складена), така що:
actualAlgorithmTime(N) e.g. "mergesort_duration(N) "
────────────────────── < constant ───────────────────── < 2.5
bound(N) N log(N)
Є багато варіантів постійних; часто "найкращий" вибір називають "постійним фактором" алгоритму ... але ми часто ігноруємо його так, як ігноруємо не найбільші терміни (див. розділ "Постійні фактори", чому вони зазвичай не мають значення). Ви також можете вважати вищезазначене рівняння як пов'язане, кажучи: " У найгіршому випадку час, який потрібно, ніколи не буде гіршим, ніж приблизно N*log(N)
, в межах коефіцієнта 2,5 (постійний фактор, який нам не дуже важливий) " .
Взагалі, O(...)
це найкорисніше, оскільки ми часто піклуємося про поведінку в гіршому випадку. Якщо f(x)
є щось "погане", як процесор або використання пам'яті, то " f(x) ∈ O(upperbound)
" означає " upperbound
це найгірший сценарій використання процесора / пам'яті".
Програми
Будучи суто математичною конструкцією, позначення big-O не обмежується розмовою про час обробки та пам'ять. Ви можете використовувати його для обговорення асимптотики всього, де масштабування має сенс, наприклад:
N
людей на вечірці ( Ɵ(N²)
конкретно N(N-1)/2
, але що важливо, це те, що вона "важить як" N²
)Приклад
На прикладі рукостискання вище, всі в кімнаті стискають руку всіх інших. У цьому прикладі #handshakes ∈ Ɵ(N²)
. Чому?
Зробіть резервну копію: кількість рукостискань точно n-select-2 або N*(N-1)/2
(кожен з N людей потискує руки N-1 іншим людям, але це подвійне врахування рукостискань так ділиться на 2):
Однак для дуже великої кількості людей лінійний термін N
є карликовим і ефективно вносить 0 у співвідношення (на графіку: частка порожніх коробок по діагоналі над загальними полями стає меншою, оскільки кількість учасників стає більшою). Тому поведінка масштабування є order N²
, або кількість рукостискань "зростає як N²".
#handshakes(N)
────────────── ≈ 1/2
N²
Це як би порожні поля діагоналі діаграми (N * (N-1) / 2 галочки) не було навіть там (N 2 галочки асимптотично).
(тимчасовий відступ від "простої англійської" :) Якщо ви хочете довести це самому, ви можете виконати просту алгебру за співвідношенням, щоб розділити її на кілька термінів ( lim
означає "розглядається в межі", просто ігноруйте її, якщо ви я цього не бачив, це просто позначення "і N дійсно дуже великий"):
N²/2 - N/2 (N²)/2 N/2 1/2
lim ────────── = lim ( ────── - ─── ) = lim ─── = 1/2
N→∞ N² N→∞ N² N² N→∞ 1
┕━━━┙
this is 0 in the limit of N→∞:
graph it, or plug in a really large number for N
tl; dr: Кількість рукостискань "виглядає як" x² настільки велика, що якби ми записували співвідношення # рукостискання / х², той факт, що нам не потрібні саме х2 рукостискання, навіть не з’явиться у десятковій частині для довільно великого часу.
наприклад, для х = 1 мільйон, співвідношення # рукостискання / х²: 0,499999 ...
Побудова інтуїції
Це дозволяє нам робити заяви, як ...
"Для досить великого розміру введення = N, незалежно від того, яким є постійний коефіцієнт, якщо я подвоїти розмір введення ...
N → (2N) = 2 ( N )
N² → (2N) ² = 4 ( N² )
cN³ → c (2N) ³ = 8 ( cN³ )
c log (N) → c log (2N) = (c log (2)) + ( c log (N) ) = (фіксована сума) + ( c log (N) )
c * 1 → c * 1
це менше, ніж O (N 1.000001 ), яке ви, можливо, хочете назвати лінійним
2 N → 2 2N = (4 N ) ............ іншим способом ...... 2 N → 2 N + 1 = 2 N 2 1 = 2 2 N
[для математично нахилених ви можете навести курсор на спойлери для другорядних сиденотів]
(з кредитом https://stackoverflow.com/a/487292/711085 )
(технічно константний фактор, можливо, може мати значення в деяких езотеричних прикладах, але я фразував речі вище (наприклад, в журналі (N)) таким, що він не відповідає)
Це хлібо-масляні замовлення зростання, які програмісти та прикладні комп'ютерні вчені використовують як орієнтири. Вони бачать це постійно. (Тож, якщо ви технічно можете подумати, що "подвійне введення робить алгоритм O (√N) в 1,414 рази повільніше", краще подумати про це як "це гірше логарифмічного, але краще, ніж лінійне".)
Постійні фактори
Зазвичай нас не хвилює, які конкретні постійні фактори, оскільки вони не впливають на те, як функція зростає. Наприклад, для виконання двох алгоритмів може знадобитися O(N)
час, але один може бути вдвічі повільнішим, ніж інший. Зазвичай ми не надто піклуємось, якщо цей фактор не дуже великий, оскільки оптимізація є складним бізнесом ( Коли оптимізація передчасна? ); також простий акт вибору алгоритму з кращим big-O часто покращує продуктивність на порядки.
Деякі асимптотично вищі алгоритми (наприклад, O(N log(log(N)))
сортування порівняння ) можуть мати настільки великий постійний коефіцієнт (наприклад 100000*N log(log(N))
) або накладні витрати, які є відносно великими, як O(N log(log(N)))
приховані + 100*N
, що їх рідко варто використовувати навіть на "великих даних".
Чому O (N) іноді найкраще ви можете зробити, тобто для чого нам потрібні структури даних
O(N)
алгоритми є певним чином "найкращими" алгоритмами, якщо вам потрібно прочитати всі ваші дані. Сам акт читання купу даних - це O(N)
операція. Завантаження в пам'ять зазвичай O(N)
(або швидше, якщо у вас апаратна підтримка, або зовсім немає часу, якщо ви вже прочитали дані). Однак якщо ви торкаєтесь або навіть переглядаєте кожну частину даних (або навіть будь-яку іншу частину даних), вашому алгоритму знадобиться O(N)
час для виконання цього вигляду. Незалежно від того, скільки часу займає ваш власний алгоритм, це буде хоча б O(N)
тому, що він витратив цей час на перегляд усіх даних.
Те саме можна сказати і для самого акта написання . Всі алгоритми, які роздруковують N речей, займуть N часу, оскільки вихід принаймні такий довгий (наприклад, друк усіх перестановок (способів переставити) набір з N ігрових карт є факторним:) O(N!)
.
Це мотивує використання структур даних : структура даних вимагає зчитувати дані лише один раз (зазвичай це O(N)
час), а також деяку кількість довільної кількості попередньої обробки (наприклад, O(N)
або O(N log(N))
або O(N²)
), яку ми намагаємось залишити невеликою. Після цього зміна структури даних (вставки / видалення / тощо) та внесення запитів на дані займають дуже мало часу, наприклад, O(1)
або O(log(N))
. Потім ви продовжуєте робити велику кількість запитів! Загалом, чим більше роботи ви готові виконати достроково, тим менше роботи вам доведеться виконати пізніше.
Наприклад, скажіть, що ви мали координати широти та довготи мільйонів відрізків доріг і хотіли знайти всі перехрестя вулиць.
O(N)
роботи лише один раз, але якщо ви хочете робити це багато разів (в даному випадку - N
раз, один раз для кожного сегмента), ми доведеться виконати O(N²)
роботу, або 1000000 ² = 1000000000000 операцій. Не добре (сучасний комп'ютер може виконувати близько мільярда операцій в секунду).O(N)
вчасно. Після цього в середньому потрібен лише постійний час, щоб шукати щось за його ключем (у цьому випадку нашим ключем є координати широти та довготи, округлені до сітки; ми шукаємо суміжні сітки, просторів яких лише 9, а це постійний).O(N²)
до керованого O(N)
, і все, що нам потрібно було зробити, - це сплатити незначні витрати, щоб зробити хеш-таблицю.Мораль історії: структура даних дозволяє нам прискорити операції. Навіть більше, розширені структури даних можуть дозволяти вам поєднувати, затримувати або навіть ігнорувати операції неймовірно розумними способами. Різні проблеми мали б різні аналогії, але всі вони передбачають організацію даних таким чином, щоб використовувати певну структуру, яка нас цікавить, або яку ми штучно наклали на неї для ведення бухгалтерського обліку. Ми робимо роботу достроково (в основному плануємо та організовуємо), і тепер повторювані завдання набагато простіше!
Практичний приклад: візуалізація порядків зростання при кодуванні
Асимптотичні позначення, по суті, зовсім окремі від програмування. Асимптотичні позначення - це математична основа для роздумів про те, як масштаби речей і як їх можна використовувати в багатьох різних сферах. Це сказало ... саме так ви застосовуєте асимптотичні позначення до кодування.
Основи: Кожного разу, коли ми взаємодіємо з кожним елементом колекції розміром A (наприклад, масивом, набором, усіма ключами карти тощо) або виконуємо ітерації циклу, це мультиплікативний коефіцієнт розміру A Чому я кажу "мультиплікативний коефіцієнт"? - тому що петлі та функції (майже за визначенням) мають мультиплікативний час роботи: кількість ітерацій, кількість робіт, виконаних у циклі (або для функцій: кількість разів, коли ви викликаєте функції, час роботи, виконаної у функції). (Це справедливо, якщо ми не робимо нічого фантазійного, як-от пропустити цикли або вийти з циклу рано чи змінити потік управління у функції на основі аргументів, що дуже часто.) Ось кілька прикладів технік візуалізації із супровідним псевдокодом.
(тут x
s представляють одиниці роботи постійного часу, інструкції процесора, опкоди інтерпретатора, що завгодно)
for(i=0; i<A; i++) // A * ...
some O(1) operation // 1
--> A*1 --> O(A) time
visualization:
|<------ A ------->|
1 2 3 4 5 x x ... x
other languages, multiplying orders of growth:
javascript, O(A) time and space
someListOfSizeA.map((x,i) => [x,i])
python, O(rows*cols) time and space
[[r*c for c in range(cols)] for r in range(rows)]
Приклад 2:
for every x in listOfSizeA: // A * (...
some O(1) operation // 1
some O(B) operation // B
for every y in listOfSizeC: // C * (...
some O(1) operation // 1))
--> O(A*(1 + B + C))
O(A*(B+C)) (1 is dwarfed)
visualization:
|<------ A ------->|
1 x x x x x x ... x
2 x x x x x x ... x ^
3 x x x x x x ... x |
4 x x x x x x ... x |
5 x x x x x x ... x B <-- A*B
x x x x x x x ... x |
................... |
x x x x x x x ... x v
x x x x x x x ... x ^
x x x x x x x ... x |
x x x x x x x ... x |
x x x x x x x ... x C <-- A*C
x x x x x x x ... x |
................... |
x x x x x x x ... x v
Приклад 3:
function nSquaredFunction(n) {
total = 0
for i in 1..n: // N *
for j in 1..n: // N *
total += i*k // 1
return total
}
// O(n^2)
function nCubedFunction(a) {
for i in 1..n: // A *
print(nSquaredFunction(a)) // A^2
}
// O(a^3)
Якщо ми зробимо щось злегка складне, ви все ще зможете візуально уявити, що відбувається:
for x in range(A):
for y in range(1..x):
simpleOperation(x*y)
x x x x x x x x x x |
x x x x x x x x x |
x x x x x x x x |
x x x x x x x |
x x x x x x |
x x x x x |
x x x x |
x x x |
x x |
x___________________|
Тут найважливіший впізнаваний контур, який ви можете намалювати, є важливим; трикутник - двовимірна форма (0,5 A ^ 2), подібно до квадрата - двовимірна форма (A ^ 2); постійний коефіцієнт двох тут залишається в асимптотичному співвідношенні між ними, однак, ми ігноруємо його, як і всі фактори ... (Є деякі невдалі нюанси цієї техніки, я не вникаю тут; вона може вас ввести в оману.)
Звичайно, це не означає, що петлі та функції погані; навпаки, вони є складовими сучасних мов програмування, і ми їх любимо. Однак ми можемо бачити, що спосіб плетіння циклів, функцій та умов роботи разом з нашими даними (контрольний потік тощо) імітує використання часу та простору нашої програми! Якщо використання часу та простору стає проблемою, то коли ми вдаємося до кмітливості та знаходимо простий алгоритм чи структуру даних, про які ми не розглядали, щоб якось зменшити порядок зростання. Тим не менше, ці методи візуалізації (хоча вони не завжди працюють) можуть дати вам наївну здогадку у найгіршому випадку.
Ось ще одна річ, яку ми можемо розпізнати візуально:
<----------------------------- N ----------------------------->
x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x
x x x x x x x x x x x x x x x x
x x x x x x x x
x x x x
x x
x
Ми можемо просто змінити це і побачити, що це O (N):
<----------------------------- N ----------------------------->
x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x
x x x x x x x x x x x x x x x x|x x x x x x x x|x x x x|x x|x
Або, можливо, ви здійснюєте журнал (N) пропусків даних за загальний час O (N * log (N)):
<----------------------------- N ----------------------------->
^ x x x x x x x x x x x x x x x x|x x x x x x x x x x x x x x x x
| x x x x x x x x|x x x x x x x x|x x x x x x x x|x x x x x x x x
lgN x x x x|x x x x|x x x x|x x x x|x x x x|x x x x|x x x x|x x x x
| x x|x x|x x|x x|x x|x x|x x|x x|x x|x x|x x|x x|x x|x x|x x|x x
v x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x
Незалежно, але варто згадати ще раз: Якщо ми виконуємо хеш (наприклад, пошук словника / хештеля), це фактор O (1). Це досить швидко.
[myDictionary.has(x) for x in listOfSizeA]
\----- O(1) ------/
--> A*1 --> O(A)
Якщо ми робимо щось дуже складне, наприклад, з рекурсивною функцією або алгоритмом розділення і перемоги, ви можете використовувати Теорему Мастера (як правило, працює), або в смішних випадках теорему Акра-Бацци (майже завжди працює) ви шукаєте вгору час роботи алгоритму у Вікіпедії.
Але програмісти не думають так, бо зрештою, інтуїція алгоритму просто стає другою природою. Ви почнете кодувати щось неефективне і одразу подумаєте "чи я роблю щось вкрай неефективне? ". Якщо відповідь "так", і ви передбачаєте, що вона насправді має значення, тоді ви можете зробити крок назад і придумати різні хитрощі, щоб зробити справи швидшими (відповідь майже завжди "використовувати хештил", рідко "використовувати дерево", і дуже рідко щось трохи складніше).
Амортизована та середньоскладова складність
Існує також поняття "амортизований" та / або "середній випадок" (зауважте, що вони різні).
Середній випадок : це не більше, ніж використання позначення big-O для очікуваного значення функції, а не сама функція. У звичайному випадку, коли ви вважаєте, що всі дані є однаково вірогідними, середній випадок - це лише середнє значення часу виконання. Наприклад, при швидкості, хоча найгірший варіант O(N^2)
для деяких дійсно поганих входів, середній випадок є звичайним O(N log(N))
(дійсно погані вхідні дані дуже малі, тому їх мало, що ми не помічаємо їх у середньому випадку).
Амортизовані найгірші випадки : деякі структури даних можуть мати найгірший рівень складності, який є великим, але гарантують, що якщо ви виконаєте багато з цих операцій, середній обсяг роботи, який ви виконуєте, буде кращим, ніж у гіршому випадку. Наприклад, у вас може бути структура даних, яка зазвичай займає постійний O(1)
час. Однак час від часу він "ікає" і потребує O(N)
часу для однієї випадкової операції, тому що, можливо, для цього потрібно зробити якусь бухгалтерію чи збирання сміття чи щось таке ... але це обіцяє вам, що якщо вона стане гикавка, вона не повторить гикавку за N більше операцій. Найгірша вартість залишається O(N)
за операцію, але амортизована вартість протягом багатьох пробіжок становить O(N)/N
=O(1)
за операцію. Оскільки великі операції є досить рідкісними, можна вважати, що велика кількість випадкових робіт поєднується з рештою роботи як постійний чинник. Ми кажемо, що робота "амортизується" при достатньо великій кількості дзвінків, що вона зникає асимптотично.
Аналогія амортизованого аналізу:
Ви керуєте машиною. Іноді вам потрібно витратити 10 хвилин на заправку, а потім витратити 1 хвилину, заправляючи бак бензином. Якщо ви робили це кожен раз, коли їхали кудись зі своїм автомобілем (витратите 10 хвилин їзди до АЗС, витратіть кілька секунд, заповнивши частину галона), це було б дуже неефективно. Але якщо ви наповнюєте бак раз на кілька днів, 11 хвилин, проведені за кермом до АЗС, «амортизуються» за достатньо велику кількість поїздок, що ви можете проігнорувати його і зробити вигляд, що всі ваші поїздки були, можливо, на 5% довше.
Порівняння середнього та амортизованого найгіршого випадку:
Хоча якщо ви з розумом переживаєте за зловмисника, є ще багато інших алгоритмічних векторів атак, яких слід турбувати, крім амортизації та середнього випадку.)
І середній регістр, і амортизація є надзвичайно корисними інструментами для роздумів та дизайну з урахуванням масштабування.
(Див. Різниця між середнім випадком та амортизованим аналізом, якщо зацікавлений у цій підтемі.)
Багатовимірний big-O
Здебільшого люди не розуміють, що на роботі є більше однієї змінної. Наприклад, в алгоритмі пошуку рядків ваш алгоритм може зайняти час O([length of text] + [length of query])
, тобто він є лінійним у двох змінних, як-от O(N+M)
. Інші більш наївні алгоритми можуть бути O([length of text]*[length of query])
або O(N*M)
. Ігнорування декількох змінних - одна з найпоширеніших оглядів, які я бачу в аналізі алгоритмів, і може спричинити перешкоди при розробці алгоритму.
Вся історія
Майте на увазі, що big-O - це не вся історія. Ви можете різко прискорити деякі алгоритми, використовуючи кешування, зробивши їх незахищеними від кешу, уникаючи вузьких місць, працюючи з оперативною пам’яттю замість диска, використовуючи паралелізацію або виконуючи роботу достроково - ці методи часто не залежать від порядку зростання Позначення "big-O", хоча ви часто бачите кількість ядер у великій O-позначці паралельних алгоритмів.
Також майте на увазі, що через приховані обмеження вашої програми ви можете не дуже піклуватися про асимптотичну поведінку. Ви можете працювати з обмеженою кількістю значень, наприклад:
O(N log(N))
швидкохідний; Ви хочете використовувати сортування вставки, яке, напевно, працює на невеликих входах. Ці ситуації часто трапляються в алгоритмах розділення і перемоги, де ви розбиваєте проблему на менші та менші підпрограми, такі як рекурсивне сортування, швидкі перетворення Фур'є або множення матриці.На практиці навіть серед алгоритмів, які мають однакові або подібні асимптотичні показники, їх відносна заслуга може бути фактично обумовлена іншими речами, такими як: інші фактори ефективності (quicksort і mergesort є обома O(N log(N))
, але quicksort використовує переваги кеш-процесорів); міркування щодо неефективності, як-от простота реалізації; чи доступна бібліотека, і наскільки поважна та підтримувана бібліотека.
Програми також працюватимуть повільніше на комп’ютері 500 МГц проти 2 ГГц. Ми насправді не розглядаємо це як частину меж ресурсів, оскільки думаємо про масштабування з точки зору машинних ресурсів (наприклад, за тактовий цикл), а не за реальну секунду. Однак є подібні речі, які можуть «таємно» вплинути на продуктивність, наприклад, чи працюєте ви під емуляцією, чи оптимізований код компілятора чи ні. Це може змусити деякі основні операції зайняти більше часу (навіть відносно один одного) або навіть прискорити або сповільнити деякі операції асимптотично (навіть відносно один одного). Ефект може бути невеликим або великим між різною реалізацією та / або оточенням. Ви перемикаєте мови чи машини, щоб не отримати цю додаткову роботу? Це залежить від ста інших причин (необхідність, навички, співробітники, продуктивність програміста,
Вищезазначені питання, як ефект від вибору мови програмування, майже ніколи не розглядаються як частина постійного чинника (а також не повинні бути); все ж слід пам’ятати про них, оскільки іноді (хоча і рідко) вони можуть впливати на речі. Наприклад, у cpython реалізація натурної черги пріоритетів є асимптотично неоптимальною ( O(log(N))
а не O(1)
для вашого вибору вставки чи знаходження-min); ви використовуєте іншу реалізацію? Напевно, ні, оскільки реалізація C, ймовірно, швидша, а подібні проблеми, мабуть, є й інші. Є компроміси; іноді вони мають значення, а іноді - ні.
( редагувати : "Завершення" простого англійського "закінчується тут.)
Додатки з математики
Для повноти точне визначення нотації big-O таке: f(x) ∈ O(g(x))
означає, що "f асимптотично верхньо обмежене const * g": ігноруючи все, що знаходиться нижче деякого кінцевого значення x, існує така константа |f(x)| ≤ const * |g(x)|
. (Інші символи такі: як і O
означає ≤, Ω
означає ≥. Існують малі варіанти: o
означає <, і ω
означає>.) f(x) ∈ Ɵ(g(x))
Означає і те, f(x) ∈ O(g(x))
і f(x) ∈ Ω(g(x))
(верхнє і нижнє обмеження g): існують деякі константи, такі що f завжди буде лежати в «смузі» між const1*g(x)
і const2*g(x)
. Це найсильніша асимптотика, яку ви можете зробити і приблизно рівнозначна==
. (Вибачте, я вирішив відкласти згадку про символи абсолютної величини до цих пір, для наочності; тим більше, що я ніколи не бачив, щоб негативні значення виникали в контексті інформатики.)
Люди часто використовуватимуть = O(...)
, що, мабуть, більш правильне позначення «comp-sci», і цілком законно використовувати; "f = O (...)" читається "f - це порядок ... / f обмежено xxx обмеженим ..." і розглядається як "f - це деякий вираз, асимптотика якого ...". Мене вчили користуватися більш суворими ∈ O(...)
. ∈
означає "є елементом" (все ще читається, як раніше). В даному конкретному випадку, O(N²)
містить такі елементи , як { 2 N²
, 3 N²
, 1/2 N²
, 2 N² + log(N)
, - N² + N^1.9
, ...} і нескінченно великою, але це все ще безліч.
O і Ω не симетричні (n = O (n²), але n² не є O (n)), а Ɵ симетричні, і (оскільки всі ці відносини є транзитивними і рефлексивними) Ɵ, отже, є симетричними і перехідними, і рефлексивними , а тому розділяє набір усіх функцій на класи еквівалентності . Клас еквівалентності - це сукупність речей, які ми вважаємо однаковими. Тобто, враховуючи будь-яку функцію, яку ви можете придумати, ви можете знайти канонічний / унікальний «асимптотичний представник» класу (загалом, взявши за межу ... я думаю ); так само, як ви можете згрупувати всі цілі числа в непарні чи рівні, ви можете згрупувати всі функції з Ɵ в x-ish, log (x) ^ 2-ish тощо), в основному ігноруючи менші терміни (але іноді ви можете застрягти з більш складні функції, які є окремими класами для себе).
=
Позначення може бути більш поширеним і навіть використовується в роботах всесвітньо відомих комп'ютерних вчених. Крім того, часто буває так, що у випадкових умовах люди скажуть, O(...)
коли мають на увазі Ɵ(...)
; це технічно вірно, оскільки набір речей Ɵ(exactlyThis)
- це підмножина O(noGreaterThanThis)
... і це простіше набрати. ;-)
EDIT: Швидке зауваження, це майже напевно плутає нотацію Big O (яка є верхньою межею) з Theta notation (яка є і верхньою, і нижньою). На мій досвід, це насправді характерно для дискусій у неакадемічних умовах. Вибачте за будь-яку причину плутанини.
В одному реченні: Зі збільшенням вашої роботи збільшується, скільки часу потрібно, щоб її виконати?
Очевидно, що це лише використання "розміру" як вхідного сигналу та "часу, взятого" на виході - така ж ідея застосовується, якщо ви хочете поговорити про використання пам'яті тощо.
Ось приклад, коли у нас є N футболок, які ми хочемо висушити. Ми припустимо, що надзвичайно швидко їх перевести в положення сушіння (тобто взаємодія людини незначна). У реальному житті це, звичайно, не так ...
Використання пральної лінії назовні: якщо припустити, що ви маєте нескінченно великий задній двір, прання висохне в O (1) час. Скільки б ви не мали, він отримає те саме сонце і свіже повітря, тому розмір не впливає на час висихання.
Використовуючи сушильну машину: ви кладете по 10 сорочок у кожне завантаження, а потім це роблять через годину. (Ігноруйте фактичну кількість тут - вони не мають значення.) Тому сушка 50 сорочок займає приблизно в 5 разів довше, ніж сушка 10 сорочок.
Поміщення всього в шафу для провітрювання: Якщо ми покладемо все в одну велику купу і просто будемо давати загальне тепло, це займе багато часу, щоб середні сорочки висохли. Я не хотів би вгадувати деталі, але я підозрюю, що це принаймні O (N ^ 2) - у міру збільшення завантаження прання час висихання збільшується швидше.
Одним з важливих аспектів позначення "великий O" є те, що він не говорить, який алгоритм буде швидшим для заданого розміру. Візьміть хешбел (рядовий ключ, ціле значення) проти масиву пар (рядок, ціле число). Чи швидше знайти ключ у хештебі чи елемент у масиві на основі рядка? . одночасно знайти запис у таблиці 100 записів, як і в таблиці 1000 000 записів. Пошук елемента в масиві (на основі вмісту, а не індексу) лінійний, тобто O (N) - в середньому вам доведеться переглянути половину записів.
Чи робить це хешштер швидшим, ніж масив для пошуку? Не обов'язково. Якщо у вас дуже невелика колекція записів, масив може виявитися швидшим - ви, можливо, зможете перевірити всі рядки за час, який потрібен для простого обчислення хеш-коду того, кого ви шукаєте. Зі збільшенням набору даних, однак, хешбел з часом переможе масив.
Big O описує верхню межу поведінки функції функції, наприклад, час виконання програми, коли входи стають великими.
Приклади:
O (n): Якщо я подвоїв розмір вводу, час виконання подвоїться
O (n 2 ): Якщо розмір вхідного сигналу збільшується вдвічі вчетверо
O (журнал n): якщо розмір введення вдвічі збільшується на одиницю
O (2 n ): Якщо розмір вводу збільшується на одиницю, час виконання подвоїться
Розмір вводу зазвичай являє собою простір у бітах, необхідний для представлення введення.
Нотація великого O найчастіше використовується програмістами як приблизна міра того, скільки часу буде потрібно для обчислення (алгоритму), вираженого як функція від розміру вхідного набору.
Big O корисно порівняти, наскільки два алгоритми збільшуватимуться у міру збільшення кількості входів.
Точніше позначення Big O використовується для вираження асимптотичної поведінки функції. Це означає, як функція поводиться, коли вона наближається до нескінченності.
У багатьох випадках "O" алгоритму потрапляє в один з наступних випадків:
Big O ігнорує фактори, які не вносять значимий внесок у криву зростання функції, оскільки розмір вводу збільшується до нескінченності. Це означає, що константи, які додаються або множать функцію, просто ігноруються.
Big O - це лише спосіб "висловити" себе звичайним способом: "Скільки часу / місця потрібно для запуску мого коду?".
Ви часто можете бачити O (n), O (n 2 ), O (nlogn) тощо, все це лише способи показати; Як змінюється алгоритм?
O (n) означає Big O - це n, і тепер ви можете подумати: "Що таке n !?" Добре "n" - кількість елементів. Зображення, яке ви хочете шукати в масиві. Ви повинні подивитися на кожен елемент і як "Ви правильний елемент / предмет?" в гіршому випадку, елемент знаходиться на останньому індексі, а це означає, що це зайняло стільки часу, скільки є елементів у списку, тому, щоб бути загальним, ми говоримо "о, ага, n - справедливо задана кількість значень!" .
Тоді ви можете зрозуміти, що означає "n 2 ", але щоб бути ще більш конкретним, пограйте з думкою, що у вас є простий, найпростіший з алгоритмів сортування; пухирці. Цей алгоритм повинен переглядати весь список для кожного елемента.
Мій список
Потік сюди буде:
Це O n 2, тому що вам потрібно переглянути всі елементи в списку, які є "n" елементами. Для кожного елемента ви переглядаєте всі елементи ще раз, для порівняння це також "n", тому для кожного елемента ви дивитесь "n" разів, що означає n * n = n 2
Я сподіваюся, що це так просто, як ви хочете.
Але пам’ятайте, Big O - це лише спосіб випробувати себе в манері часу та простору.
Big O описує основний характер алгоритму масштабування.
Є багато інформації, що Big O не розповідає вам про заданий алгоритм. Він скорочується до кісток і надає лише інформацію про характер масштабування алгоритму, зокрема про те, як масштабується використання ресурсів (думаю, час або пам'ять) алгоритму у відповідь на "розмір вводу".
Розглянемо різницю між паровим двигуном і ракетою. Вони не просто різні різновиди однієї і тієї ж речі (як, скажімо, двигун Prius проти двигуна Lamborghini), але вони є кардинально різними видами приводних систем, по суті їх. Парова машина може бути швидшою, ніж іграшкова ракета, але жоден паровий поршневий двигун не зможе досягти швидкості орбітального ракетного апарату. Це пояснюється тим, що ці системи мають різні характеристики масштабування щодо співвідношення необхідного палива ("витрата ресурсів") для досягнення заданої швидкості ("розмір входу").
Чому це так важливо? Тому що програмне забезпечення вирішує проблеми, які можуть відрізнятися за розміром за факторами до трильйона. Подумайте на хвилинку. Співвідношення між швидкістю, необхідною для подорожі до Місяця, і швидкістю ходьби людини менше 10 000: 1, і це абсолютно невелике порівняно з діапазоном програмного забезпечення розмірів. Оскільки програмне забезпечення може зіткнутися з астрономічним діапазоном розмірів вхідних даних, існує велика складність алгоритму Big O, це фундаментальний характер масштабування, щоб довести будь-які деталі реалізації.
Розглянемо приклад канонічного сортування. Сортування міхура - O (n 2 ), а сортування - O (n log n). Скажімо, у вас є два додатки для сортування, додаток A, який використовує сортування бульбашок, і додаток B, який використовує сортування злиття, і скажімо, що для розмірів вводу приблизно 30 елементів додаток A на 1000 разів швидше, ніж додаток B при сортуванні. Якщо вам ніколи не доведеться сортувати набагато більше 30 елементів, то очевидно, що вам слід віддати перевагу застосуванню A, оскільки це набагато швидше при цих розмірах введення. Однак, якщо ви виявите, що вам, можливо, доведеться сортувати десять мільйонів елементів, то, що ви очікували, це те, що програма B в цьому випадку в кінцевому підсумку в тисячі разів швидша, ніж додаток А, повністю завдяки тому, як масштабується кожен алгоритм.
Ось звичайний англійський бестіарій, який я схильний використовувати при поясненні поширених різновидів Big-O
У всіх випадках віддайте перевагу алгоритмам, які перебувають вище у списку, ніж тим, які знаходяться нижче. Однак вартість переходу на більш дорогий клас складності значно варіюється.
O (1):
Ніякого зростання. Незалежно від того, наскільки велика проблема, ви можете вирішити її за стільки ж часу. Це дещо аналогічно мовлення, коли для передачі на певній відстані потрібно стільки ж енергії, незалежно від кількості людей, які лежать у межах радіомовлення.
O (журнал n ):
Ця складність така ж, як O (1), за винятком того, що вона лише трохи гірша. Для всіх практичних цілей ви можете вважати це дуже великим постійним масштабуванням. Різниця в роботі між обробкою 1 тис. І 1 млрд. Предметів - лише шість факторів.
O ( n ):
Вартість вирішення проблеми пропорційна розміру проблеми. Якщо ваша проблема подвоюється за розмірами, то вартість рішення подвоюється. Оскільки більшість проблем доводиться певним чином сканувати на комп’ютер, як введення даних, читання диска або мережевий трафік, це, як правило, доступний коефіцієнт масштабування.
O ( n журналу n ):
Ця складність дуже схожа на O ( n ) . Для всіх практичних цілей обидва рівнозначні. Цей рівень складності, як правило, все ще вважається масштабним. Встановлюючи припущення, деякі алгоритми O ( n log n ) можуть бути перетворені в O ( n ) алгоритми. Наприклад, обмеження розміру клавіш зменшує сортування від O ( n log n ) до O ( n ) .
O ( n 2 ):
Зростає як квадрат, де n - довжина сторони квадрата. Це той самий темп зростання, що і "ефект мережі", коли всі в мережі можуть знати всіх інших в мережі. Зростання дорого. Більшість масштабованих рішень не можуть використовувати алгоритми з таким рівнем складності, не роблячи значної гімнастики. Це, як правило, стосується і всіх інших складностей поліномів - O ( n k ) .
O (2 n ):
Не масштабує. Ви не маєте надії вирішити будь-яку проблему нетривіального розміру. Корисно для того, щоб знати, чого слід уникати, і для експертів знайти приблизні алгоритми, які знаходяться в O ( n k ) .
Big O - це міра того, скільки часу / простору використовує алгоритм щодо розміру його введення.
Якщо алгоритмом є O (n), час / простір збільшуватиметься з тією ж швидкістю, що і його вхід.
Якщо алгоритмом є O (n 2 ), то час / простір збільшуються зі швидкістю введення його в квадрат.
і так далі.
Що таке просте англійське пояснення Big O? З якомога меншим формальним визначенням і простою математикою.
Просте англійське пояснення необхідності нотації Big-O:
Коли ми програмуємо, ми намагаємося вирішити проблему. Те, що ми кодуємо, називається алгоритмом. Позначення Big O дозволяють порівняти гірші показники ефективності наших алгоритмів у стандартизованому вигляді. Технічні характеристики обладнання змінюються з часом, і вдосконалення обладнання може скоротити час роботи алгоритмів. Але заміна апаратних засобів не означає, що наш алгоритм з часом поліпшується або вдосконалюється, оскільки наш алгоритм все одно той же. Отже, щоб ми могли порівняти різні алгоритми, визначити, чи кращий чи ні, ми використовуємо нотацію Big O.
Просте англійське пояснення, що таке велика нотація:
Не всі алгоритми працюють за однаковий проміжок часу і можуть змінюватися залежно від кількості елементів у вхідних даних, які ми будемо називати n . Виходячи з цього, ми розглядаємо гірший аналіз випадку або верхню межу часу виконання, оскільки n стає все більшим і більшим. Ми повинні усвідомлювати, що таке n , тому що багато з нотацій Big O посилаються на це.
Дуже важко виміряти швидкість програмних програм, і коли ми намагаємось, відповіді можуть бути дуже складними та наповненими винятками та особливими випадками. Це велика проблема, тому що всі ці винятки та особливі випадки відволікають і не допомагають, коли ми хочемо порівняти дві різні програми між собою, щоб з’ясувати, яка «найшвидша».
В результаті всієї цієї неприємної складності люди намагаються описати швидкість програмних програм, використовуючи найменші та найменш складні (математичні) вирази. Ці вирази є дуже грубими наближеннями: хоча за допомогою трохи удачі вони захоплять "суть" того, чи є частиною програмного забезпечення швидкий чи повільний.
Оскільки вони є наближеннями, ми використовуємо букву "O" (Big Oh) у виразі, як умову, щоб сигналізувати читачеві, що ми робимо грубе надмірне спрощення. (І щоб переконатися, що ніхто помилково не думає, що вираз є якимось чином точним).
Якщо ви прочитаєте "О" як значення "на порядок" або "приблизно", ви не помилитеся. (Я думаю, що вибір Біг-О, можливо, був спробою гумору).
Єдине, що намагаються зробити ці вирази "Big-Oh" - це описати, наскільки програмне забезпечення сповільнюється, коли ми збільшуємо кількість даних, які програмне забезпечення має обробляти. Якщо ми подвоїмо кількість даних, які потрібно обробити, чи потрібне програмне забезпечення вдвічі довше, щоб закінчити роботу? Десять разів довше? На практиці існує дуже обмежена кількість виразів із великим ой, з якими ви зіткнетесь і про які потрібно хвилюватися:
Добре:
O(1)
Постійний : Програма займає той же час для запуску незалежно від того, наскільки великий вхід.O(log n)
Логарифмічний : Час виконання програми збільшується лише повільно, навіть при великих збільшеннях розміру вводу.Погане:
O(n)
Лінійний : Час виконання програми збільшується пропорційно розміру вводу.O(n^k)
Поліном : - Час обробки зростає все швидше і швидше - як поліноміальна функція - зі збільшенням розміру вводу.... і потворне:
O(k^n)
Експонентний Час виконання програми збільшується дуже швидко, навіть при помірному збільшенні масштабу проблеми - обробляти невеликі набори даних за допомогою експоненціальних алгоритмів практично практично.O(n!)
Факторний Час роботи програми буде довше, ніж ви можете дозволити собі чекати чого завгодно, крім самих маленьких і найтривіальніших наборів даних.O(n log n)
який би вважався хорошим.
Простий прямої відповіді може бути:
Big O являє найгірший можливий час / простір для цього алгоритму. Алгоритм ніколи не займе більше місця / часу вище цієї межі. Big O являє собою складність часу та простору в крайньому випадку.
Гаразд, мої 2 центи.
Big-O, це темп збільшення ресурсу, що споживається програмою, wrt проблема-екземпляр-розмір
Ресурс: міг бути загальний час процесора, максимум місця в оперативній пам'яті. За замовчуванням відноситься до часу процесора.
Скажіть, проблема: "Знайти суму",
int Sum(int*arr,int size){
int sum=0;
while(size-->0)
sum+=arr[size];
return sum;
}
problem-instance = {5,10,15} ==> проблема-примірник-розмір = 3, ітерації в циклі = 3
problem-instance = {5,10,15,20,25} ==> проблема-екземпляр-розмір = 5 ітерацій в циклі = 5
Для введення розміру "n" програма зростає зі швидкістю "n" ітерацій у масиві. Отже, Big-O є N, вираженим як O (n)
Скажіть, проблема - "Знайдіть комбінацію",
void Combination(int*arr,int size)
{ int outer=size,inner=size;
while(outer -->0) {
inner=size;
while(inner -->0)
cout<<arr[outer]<<"-"<<arr[inner]<<endl;
}
}
problem-instance = {5,10,15} ==> проблема-примірник-розмір = 3, сумарні ітерації = 3 * 3 = 9
problem-instance = {5,10,15,20,25} ==> проблема-примірник-розмір = 5, загальна кількість ітерацій = 5 * 5 = 25
Для введення розміру "n" програма зростає зі швидкістю "n * n" ітерацій у масиві. Отже, Big-O є N 2, вираженим як O (n 2 )
Позначення Big O - це спосіб опису верхньої межі алгоритму з точки зору простору або часу роботи. N - кількість елементів у задачі (тобто розмір масиву, кількість вузлів на дереві тощо). Ми зацікавлені описати час роботи, коли n стає більшим.
Коли ми кажемо, що деяким алгоритмом є O (f (n)), ми говоримо, що час роботи (або необхідний простір) за цим алгоритмом завжди нижчий, ніж деякий постійний час f (n).
Сказати, що бінарний пошук має час роботи O (logn) - це означає, що існує деяка константа c, яку ви можете помножити log (n) на те, що завжди буде більшим, ніж час виконання бінарного пошуку. У цьому випадку у вас завжди буде якийсь постійний коефіцієнт порівняння log (n).
Іншими словами, де g (n) - час роботи вашого алгоритму, ми говоримо, що g (n) = O (f (n)), коли g (n) <= c * f (n), коли n> k, де c і k - деякі константи.
" Що таке просте англійське пояснення Big O? З якомога меншим формальним визначенням і простою математикою ".
Таке красиво просте і коротке запитання, здається, принаймні заслуговує настільки ж короткої відповіді, як це може отримати студент під час репетиторства.
Велика нотація просто говорить про те, скільки часу * алгоритм може працювати протягом лише кількості вхідних даних **.
(* в чудовому, без одиниці відчутті часу!)
(** що важливо, тому що люди завжди хочуть більше , чи живуть вони сьогодні, чи завтра)
Ну, що так чудового в нотації Big O, якщо саме це і робиться?
Практично кажучи, аналіз Big O є настільки корисним і важливим, оскільки Big O прямо фокусує увагу на власній складності алгоритму і повністю ігнорує все, що є лише константою пропорційності - наприклад, двигуном JavaScript, швидкістю процесора, підключенням до Інтернету та ін. всі ті речі , які стають швидко стають застарілими , як сміховинно як Model T . Big O фокусується на продуктивності лише в тому, що має значення однаково стільки ж, скільки людей, які живуть в теперішньому або майбутньому.
Нотація Big O також просвічує прожектор безпосередньо на найважливішому принципі комп’ютерного програмування / інженерії, факт, який надихає всіх хороших програмістів продовжувати думати і мріяти: єдиний спосіб досягти результатів поза повільним маршовим маршем технологій - винаходити краще алгоритм .
Приклад алгоритму (Java):
// given a list of integers L, and an integer K
public boolean simple_search(List<Integer> L, Integer K)
{
// for each integer i in list L
for (Integer i : L)
{
// if i is equal to K
if (i == K)
{
return true;
}
}
return false;
}
Опис алгоритму:
Цей алгоритм шукає список, по пунктах, шукає ключ,
Ітерація кожного елемента в списку, якщо це ключ, то поверніть True,
Якщо цикл закінчився, не знайшовши ключ, поверніть False.
Позначення Big-O представляють верхню межу Складності (Час, Простір, ..)
Щоб знайти Big-O про складність у часі:
Обчисліть, скільки часу (щодо розміру вводу) займає найгірший випадок:
Найгірше: ключ у списку не існує.
Час (найгірший випадок) = 4n + 1
Час: O (4n + 1) = O (n) | у Біг-О константи нехтують
O (n) ~ лінійний
Є також Big-Omega, які представляють складність Best-Case:
Кращий випадок: ключ - це перший пункт.
Час (найкращий випадок) = 4
Час: Ω (4) = O (1) ~ Миттєвий \ Постійний
C
було б краще
Великий О
f (x) = O ( g (x)), коли x переходить до a (наприклад, a = + ∞), означає, що існує функція k така, що:
f (x) = k (x) g (x)
k обмежений у деякому сусідстві з a (якщо a = + ∞, це означає, що є числа N і M такі, що для кожного x> N, | k (x) | <M).
Іншими словами, простою англійською мовою: f (x) = O ( g (x)), x → a, означає, що в сусідстві з a f розпадається на добуток g і деяку обмежену функцію.
Малий о
До речі, ось для порівняння визначення малого о.
f (x) = o ( g (x)), коли x переходить до означає, що існує функція k така, що:
f (x) = k (x) g (x)
k (x) переходить до 0, коли x переходить до a.
Приклади
sin x = O (x), коли x → 0.
sin x = O (1), коли x → + ∞,
x 2 + x = O (x), коли x → 0,
x 2 + x = O (x 2 ), коли x → + ∞,
ln (x) = o (x) = O (x), коли x → + ∞.
Увага! Позначення зі знаком рівності "=" використовує "фальшиву рівність": правда, що o (g (x)) = O (g (x)), але помилково, що O (g (x)) = o (g (х)). Аналогічно, нормально писати "ln (x) = o (x), коли x → + ∞", але формула "o (x) = ln (x)" не мала б сенсу.
Більше прикладів
O (1) = O (n) = O (n 2 ), коли n → + ∞ (але не навпаки, рівність "фальшива"),
O (n) + O (n 2 ) = O (n 2 ), коли n → + ∞
O (O (n 2 )) = O (n 2 ), коли n → + ∞
O (n 2 ) O (n 3 ) = O (n 5 ), коли n → + ∞
Ось стаття у Вікіпедії: https://en.wikipedia.org/wiki/Big_O_notation
Позначення Big O - це спосіб опису швидкості запуску алгоритму із заданою кількістю вхідних параметрів, які ми будемо називати "n". Це корисно в галузі інформатики, оскільки різні машини працюють з різною швидкістю, і просто сказати, що алгоритм займає 5 секунд, це не дуже розкаже вам, тому що, хоча ви можете працювати з системою з октоядерним процесором 4,5 ГГц, я можу працювати 15-річна система 800 МГц, яка може зайняти більше часу незалежно від алгоритму. Отже, замість того, щоб вказувати, наскільки швидко алгоритм працює за часом, ми говоримо, наскільки швидко він працює за кількістю вхідних параметрів або "n". Описуючи алгоритми таким чином, ми можемо порівнювати швидкості алгоритмів, не враховуючи швидкості роботи самого комп’ютера.
Я не впевнений, що я продовжую займатися цією темою, але все-таки думав, що поділюсь: я одного разу знайшов цю сторінку в блозі, щоб мати кілька корисних (хоча дуже базових) пояснень та прикладів на Big O:
Через приклади це допомогло отримати голі основи в моєму черепахоподібному черепі, тому я думаю, що це досить 10-хвилинне читання, яке дозволяє вам попрямувати в правильному напрямку.
Ви хочете знати все, що вам потрібно знати про великий O? Я теж.
Тому, щоб говорити про велику О, я буду використовувати слова, які мають лише один удар у них. Один звук на слово. Маленькі слова швидкі. Ви знаєте ці слова, як і я. Ми будемо використовувати слова з одним звуком. Вони маленькі. Я впевнений, що ви будете знати всі слова, які ми будемо використовувати!
Тепер давайте ми з вами поговоримо про роботу. Більшу частину часу я не люблю роботу. Вам подобається робота? Можливо, ви так і робите, але я впевнений, що ні.
Я не люблю ходити на роботу. Я не люблю проводити час на роботі. Якби я мав свій шлях, я хотів би просто грати і робити веселі речі. Ви відчуваєте те саме, що і я?
Зараз часом мені доводиться ходити на роботу. Це сумно, але правда. Отже, коли я на роботі, у мене є правило: я намагаюся робити менше роботи. Настільки ж, що я не можу працювати. Тоді я йду грати!
Тож ось головна новина: великий О може допомогти мені не працювати! Я можу грати більше часу, якщо знаю велику О. Менше роботи, більше гри! Ось що мені допомагає великий О.
Зараз у мене є робота. У мене цей список: один, два, три, чотири, п’ять, шість. Я мушу додати в цей список усі речі.
Вау, я ненавиджу роботу. Але добре, я маю це зробити. Так ось я йду.
Один плюс два - це три… плюс три - це шість… і чотири - це… Не знаю. Я загубився. Мені це занадто важко робити в голові. Я не дуже переймаюся такою роботою.
Тож не давайте робити роботу. Давайте ми з вами просто подумаємо, як це важко. Скільки роботи мені доведеться зробити, щоб додати шість чисел?
Ну, подивимось. Я повинен додати одне і два, а потім додати до трьох, а потім додати до чотирьох… Загалом, я рахую шість додань. Я повинен зробити шість доповнень, щоб вирішити це.
Ось велике О, щоб сказати нам, наскільки важка ця математика.
Big O каже: ми повинні зробити шість доповнень, щоб вирішити це. Одне додавання для кожної речі від одного до шести. Шість невеликих шматочків роботи ... кожен біт роботи - це одна добавка.
Ну, я не зроблю роботу над їх додаванням зараз. Але я знаю, як важко було б. Це було б шість доповнень.
О ні, зараз у мене більше роботи. Шиш. Хто робить такі речі ?!
Тепер вони просять мене додати від одного до десяти! Навіщо мені це робити? Я не хотів додавати один до шести. Додати від одного до десяти… ну… це було б ще важче!
Наскільки важче це було б? Скільки ще роботи мені доведеться зробити? Чи потрібні мені більш-менш кроки?
Ну, я думаю, мені доведеться зробити десять доповнень ... по одній на кожну річ від одного до десяти. Десять - більше шести. Мені доведеться попрацювати набагато більше, щоб додати від одного до десяти, ніж один до шести!
Я не хочу зараз додавати. Я просто хочу подумати над тим, як важко було б додати стільки. І, сподіваюся, зіграю, як тільки зможу.
Додати від одного до шести, це деяка робота. Але ви бачите, щоб додати від одного до десяти, це більше роботи?
Великий О - твій друг і мій. Big O допомагає нам подумати над тим, скільки роботи нам належить зробити, щоб ми могли планувати. І якщо ми дружимо з великим О, він може допомогти нам вибрати роботу, яка не така вже й важка!
Тепер ми повинні зробити нову роботу. О ні. Мені ця робота зовсім не подобається.
Нова робота така: додайте всі речі від одного до n.
Зачекайте! Що таке n? Я пропустив це? Як я можу додати від одного до n, якщо ви не скажете мені, що таке n?
Ну, я не знаю, що таке російська. Мені не сказали. Чи були ви? Ні? Ну добре. Тому ми не можемо виконати роботу. Вау.
Але хоч ми не будемо виконувати цю роботу зараз, ми можемо здогадатися, як важко це було б, якби ми знали російську. Нам доведеться скласти російські речі, правда? Звичайно!
Тепер ось великий О, і він розповість нам, наскільки важка ця робота. Він каже: додати всі речі від одного до N, по черзі - це O (n). Щоб додати всі ці речі, [я знаю, я повинен додати n разів.] [1] Це велике О! Він розповідає, як важко виконувати якусь роботу.
Як на мене, я думаю про велику О, як про велику, повільну, бос-людину. Він думає про роботу, але цього не робить. Він може сказати: "Ця робота швидка". Або, він може сказати: "Ця робота така повільна і важка!" Але він не виконує роботи. Він просто дивиться на роботу, а потім каже нам, скільки часу це може зайняти.
Мені багато догляду за великим О. Чому? Я не люблю працювати! Ніхто не любить працювати. Ось чому ми всі любимо велику О! Він говорить нам, як швидко ми можемо працювати. Він допомагає нам думати, наскільки важка робота.
О, більше роботи. Тепер не давайте робити роботу. Але, давайте складемо план, як це зробити, крок за кроком.
Вони дали нам колоду з десяти карт. Всі вони змішані: сім, чотири, два, шість… зовсім не прямо. А тепер ... наша робота - сортувати їх.
Ergh. Це звучить як багато роботи!
Як ми можемо сортувати цю колоду? У мене є план.
Я перегляну кожну пару карт, пару за парами, через колоду, від першої до останньої. Якщо перша картка в одній парі велика, а наступна картка в цій парі - маленька, я поміняю їх. Ще, я переходжу до наступної пари, і так далі, і так далі ... і незабаром колода робиться.
Коли колоду зроблено, я запитую: чи я поміняв картки в той пропуск? Якщо так, я мушу це зробити ще раз, зверху.
У якийсь момент, в якийсь час не буде жодних підкачок, і наш вид колоди буде зроблено. Стільки роботи!
Ну, скільки б роботи було, щоб сортувати картки за тими правилами?
У мене є десять карток. І більшу частину часу - тобто, якщо мені не пощастить - я повинен пройти всю колоду до десяти разів, маючи до десяти заміни карт кожного разу через колоду.
Великий О, допоможи мені!
Великий O заходить і каже: для колоди з n карт, сортування його таким чином буде зроблено за O (N квадрат).
Чому він каже n квадрат?
Що ж, ви знаєте, що n квадрата n разів n. Тепер я розумію: перевірено n карт, аж до колоди, яка може бути n разів. Це дві петлі, кожна з яких має n кроків. Тобто на квадраті ще багато роботи, яку потрібно виконати. Багато роботи, точно!
Тепер, коли великий O каже, що це займе роботу O (n квадрата), він не означає, що n додає квадрат, на ніс. Це може бути трохи менше, для якогось випадку. Але в гіршому випадку для сортування колоди буде близько п яти кроків роботи.
Тепер ось, де великий О - наш друг.
Великий O вказує на це: коли n стає великим, коли ми сортуємо картки, робота отримує МНОГО БОЛЬШЕ ЗАВДАННЯ, ніж стара робота, що додає ці речі. Звідки ми це знаємо?
Ну а якщо n стає справді великим, нам не байдуже, що ми можемо додати до n чи n у квадрат.
Для великих n n квадрат більше, ніж n.
Big O каже нам, що сортувати речі важче, ніж додавати речі. O (n квадрата) більше O (n) для великого n. Це означає: якщо n стає справді великим, для сортування змішаної колоди з російських речей ОБОВ'ЯЗКОВО знадобиться більше часу, ніж просто додати n змішаних речей.
Big O не вирішує роботу для нас. Big O каже нам, наскільки важка робота.
У мене колода карт. Я їх сортував. Ви допомогли. Дякую.
Чи існує більш швидкий спосіб сортування карт? Чи може нам великий О допомогти?
Так, є більш швидкий шлях! Навчитися потрібно деякий час, але це працює ... і це працює досить швидко. Ви також можете спробувати, але витрачайте час на кожен крок і не втрачайте свого місця.
У цьому новому способі сортування колоди ми не перевіряємо пари карт, як ми це робили деякий час тому. Ось ваші нові правила сортування цієї колоди:
Перший: я вибираю одну карту в тій частині колоди, над якою зараз працюємо. Ви можете вибрати для мене, якщо вам подобається. (Перший раз, коли ми це робимо, "частина колоди, над якою ми працюємо зараз" - це, звичайно, вся колода.)
Двоє: я заграю колоду на ту карту, яку ви вибрали. Що це за хитрість; як я граю? Ну, я йду від стартової карти вниз, по черзі, і шукаю карту, яка вище, ніж карта splay.
Третє: Я йду від торцевої картки вгору, і шукаю карту, нижчу, ніж карта splay.
Як тільки я знайшов ці дві картки, я поміняю їх і продовжую шукати більше карт, які можна поміняти. Тобто, я повертаюсь до другого кроку і граю на картці, яку ви вибрали ще трохи.
У якийсь момент ця петля (від двох до трьох) закінчиться. Він закінчується, коли обидві половини цього пошуку зустрічаються на картці splay. Потім ми просто переграли колоду карткою, яку ви вибрали на першому кроці. Тепер усі картки, що знаходяться біля початку, нижчі, ніж картки splay; а картки, що знаходяться в кінці, більш високі, ніж картки splay. Класна хитрість!
Чотири (і це найцікавіша частина): У мене зараз дві невеликі колоди, одна нижча, ніж карта splay, і ще одна висока. Тепер я переходжу до першого кроку, на кожній невеликій колоді! Тобто я починаю з першого кроку на першій невеликій колоді, і коли ця робота виконана, я починаю з першого кроку на наступній невеликій колоді.
Я розбиваю колоду по частинах і сортую кожну частину, більш дрібну і більш маленьку, і в якийсь час мені більше роботи не залишається. Тепер це може здатися повільним, з усіма правилами. Але повірте, це зовсім не повільно. Це набагато менше роботи, ніж перший спосіб сортування речей!
Як називається цей сорт? Це називається Швидкий Сортування! Такий сорт зробив чоловік на ім'я CAR Hoare, і він назвав його Швидким Сортуванням. Тепер Швидкий сорт звикає весь час!
Швидкий сортування розбиває великі колоди на маленькі. Тобто це розбиває великі завдання у малих.
Хммм. Можливо, там є правило, я думаю. Щоб великі завдання були маленькими, розбийте їх.
Цей сорт досить швидкий. Як швидко? Big O говорить нам: цей сорт потребує роботи O (n log n), в середньому випадку.
Це більш-менш швидко, ніж перший сорт? Великий О, будь ласка, допоможіть!
Першим сортом був O (n квадрат). Але швидкий сортування - це O (n log n). Ви знаєте, що n log n менше n квадрата, для великого n, правда? Ну, ось так ми знаємо, що Швидкий сортування швидкий!
Якщо вам доведеться сортувати колоду, який найкращий спосіб? Ну, ти можеш робити те, що хочеш, але я вибрав би Швидкий сортування.
Чому я вибираю Швидкий сортування? Я не люблю працювати, звичайно! Я хочу, щоб робота була зроблена, як тільки я можу її виконати.
Як дізнатись, що Швидкий сортування - це менше роботи? Я знаю, що O (n log n) менший, ніж O (n квадрат). О є більш малі, тому Швидкий Сортування - це менше роботи!
Тепер ви знаєте, мій друг, Великий О. Він допомагає нам робити менше роботи. І якщо ви знаєте велику О, ви можете також менше працювати!
Ти все це дізнався зі мною! Ти така розумна! Дуже дякую!
Тепер, коли робота закінчена, давайте пограємо!
[1]: Є спосіб обдурити і додати всі речі від одного до n, і все за один раз. Якийсь дитина на ім’я Гаус дізнався це, коли йому було вісім. Я не такий розумний, тому не питайте мене, як він це зробив .
У мене більш простий спосіб зрозуміти часову складність, яку він найпоширеніший показник для обчислення складності часу - це позначення Big O. Це видаляє всі постійні фактори, щоб час роботи можна було оцінити по відношенню до N, оскільки N наближається до нескінченності. Загалом ви можете подумати про це так:
statement;
Постійна. Час запуску заяви не зміниться стосовно N
for ( i = 0; i < N; i++ )
statement;
Є лінійним. Час роботи циклу прямо пропорційне N. Коли N подвоюється, то і час виконання.
for ( i = 0; i < N; i++ )
{
for ( j = 0; j < N; j++ )
statement;
}
Квадратний. Час виконання двох циклів пропорційно квадрату N. Коли N подвоюється, час роботи збільшується на N * N.
while ( low <= high )
{
mid = ( low + high ) / 2;
if ( target < list[mid] )
high = mid - 1;
else if ( target > list[mid] )
low = mid + 1;
else break;
}
Є логарифмічним. Час виконання алгоритму пропорційно кількості разів N можна розділити на 2. Це пояснюється тим, що алгоритм ділить робочу зону навпіл з кожною ітерацією.
void quicksort ( int list[], int left, int right )
{
int pivot = partition ( list, left, right );
quicksort ( list, left, pivot - 1 );
quicksort ( list, pivot + 1, right );
}
Чи N * log (N). Час виконання складається з N циклів (ітеративних або рекурсивних), які є логарифмічними, таким чином алгоритм є комбінацією лінійної та логарифмічної.
Взагалі, робити щось із кожним предметом в одному вимірі лінійно, робити щось з кожним предметом у двох вимірах є квадратичним, а ділити робочу зону навпіл - логарифмічно. Є й інші заходи Big O, такі як кубічний, експоненціальний та квадратний корінь, але вони майже не такі поширені. Нотація великого O описується як O (), де міра. Алгоритм quicksort був би описаний як O (N * log (N)).
Примітка. Ніщо з цього не враховувало найкращих, середніх та найгірших заходів. У кожного буде своя велика нотація O. Також зауважте, що це ДУЖЕ спрощене пояснення. Великий О - це найпоширеніший, але він також більш складний, що я показав. Існують також інші позначення, такі як велика омега, маленька о та велика тета. Ви, мабуть, не зустрінете їх поза курсом аналізу алгоритмів.
Скажімо, ви замовляєте Гаррі Поттера: Повна колекція 8 фільмів [Blu-ray] від Amazon та завантажуйте ту саму колекцію фільмів в Інтернеті одночасно. Ви хочете перевірити, який метод швидший. Для доставки потрібно майже добу, а завантаження завершено приблизно на 30 хвилин раніше. Чудово! Так що це жорстка гонка.
Що робити, якщо я замовляю декілька фільмів Blu-ray, таких як "Володар кілець", "Сутінки", "Трилогія темного лицаря" тощо, і завантажую всі фільми в Інтернет одночасно? Цього разу на доставку ще потрібно добу, але на завантаження онлайн потрібно три дні. Для інтернет-магазинів кількість придбаних товарів (вхідних даних) не впливає на час доставки. Вихід постійний. Назвемо це О (1) .
Для завантаження в Інтернеті час завантаження прямо пропорційний розмірам файлів фільму (вхід). Ми називаємо це О (n) .
З експериментів ми знаємо, що онлайн-торгові ваги краще, ніж завантаження в Інтернеті. Дуже важливо зрозуміти велику нотацію O, оскільки вона допомагає проаналізувати масштабованість та ефективність алгоритмів.
Примітка: Позначення Big O являє собою найгірший сценарій алгоритму. Припустимо, що O (1) і O (n) є найгіршими сценаріями наведеного вище прикладу.
Довідка : http://carlcheo.com/compsci
Припустимо, ми говоримо про алгоритм A , який повинен робити щось із набором даних розміру n .
Тоді O( <some expression X involving n> )
простою англійською мовою означає:
Якщо вам не пощастить під час виконання A, може знадобитися стільки, скільки X (n) операцій.
Як це відбувається, є певні функції (думати про них як реалізації в X (п) ) , які , як правило, зустрічаються досить часто. Вони добре відомі і легко порівняти (Приклади: 1
, Log N
, N
, N^2
, N!
і т.д ..)
Порівнюючи їх, коли йдеться про A та інші алгоритми, легко класифікувати алгоритми за кількістю операцій, які вони можуть (в гіршому випадку) вимагати для виконання.
Загалом, нашою метою буде знайти або структурувати алгоритм А таким чином, щоб він мав функцію, X(n)
яка повертає якомога менше число.
Якщо у вас є відповідна концепція нескінченності в голові, то є дуже короткий опис:
Велика нотація O каже вам вартість вирішення нескінченно великої проблеми.
І тим більше
Постійні фактори незначні
Якщо ви оновите до комп'ютера, який може запустити ваш алгоритм удвічі швидше, велика нотація O цього не помітить. Постійні поліпшення факторів занадто малі, щоб їх можна було помітити в масштабі, з яким працює велика нотація O. Зауважимо, що це навмисна частина дизайну великих O позначень.
Хоча можна виявити що-небудь "більше", ніж постійний коефіцієнт.
Якщо вам цікаво робити обчислення, розмір яких досить "великий", щоб вважати його приблизно нескінченним, то велике позначення O - це приблизно вартість вирішення вашої проблеми.
Якщо вищезазначене не має сенсу, то у вас немає сумісного інтуїтивного поняття нескінченності в голові, і ви, ймовірно, повинні нехтувати всім вищесказаним; єдиний спосіб, коли я знаю зробити ці ідеї суворими або пояснити їх, якщо вони вже не є інтуїтивно корисними, - це спочатку навчити вас великій нотації O або чомусь подібному. (хоча, як тільки ви добре зрозумієте велику нотацію O в майбутньому, можливо, варто переглянути ці ідеї)
Що таке просте англійське пояснення позначення "Big O"?
Дуже швидка примітка:
О в "Великому О" позначається як "Порядок" (а саме "порядок"),
щоб ви могли буквально зрозуміти, що він використовується для того, щоб замовити щось для їх порівняння.
"Big O" робить дві речі:
Notations
.Існує сім найпоширеніших позначень
1
кроком, це чудово, упорядкований №1logN
кроками, його добре, впорядковано №2N
кроками, його ярмарок, Наказ №3O(NlogN)
кроками, це не добре, Замовлення №4N^2
виконайте завдання, виконане кроками, це погано, Наказ №52^N
виконайте завдання, виконане кроками, це жахливо, наказ №6N!
кроками, це жахливо, Наказ №7Припустимо, ви отримали позначення O(N^2)
, що ви не тільки зрозуміли, що метод виконує N * N кроків для виконання завдання, також ви бачите, що це не добре, як O(NlogN)
з його рейтингу.
Будь ласка, зверніть увагу на замовлення в кінці рядка, просто для кращого розуміння. Існує більше 7 позначень, якщо всі можливості розглядаються.
У CS набір кроків для виконання завдання називається алгоритмами.
У термінології нотація Big O використовується для опису продуктивності або складності алгоритму.
Крім того, Big O встановлює найгірший випадок або вимірює верхні кроки.
Ви можете звернутися до Big-Ω (Big-Omega) для кращого випадку.
Позначення Big-Ω (Big-Omega) (стаття) | Академія хана
Підсумок
"Big O" описує роботу алгоритму та оцінює його.
або адресувати його формально, "Big O" класифікує алгоритми та стандартизує процес порівняння.
Найпростіший спосіб поглянути на це (простою англійською)
Ми намагаємось побачити, як кількість вхідних параметрів впливає на час роботи алгоритму. Якщо час роботи вашої програми пропорційний кількості вхідних параметрів, то, як кажуть, вона є великою з n.
Викладене вище твердження - хороший початок, але не зовсім правдиве.
Більш точне пояснення (математичне)
Припустимо
n = кількість вхідних параметрів
T (n) = фактична функція, яка виражає час виконання алгоритму як функцію n
c = константа
f (n) = приблизна функція, яка виражає час виконання алгоритму як функцію n
Тоді, що стосується великого O, наближення f (n) вважається досить хорошим, доки є нижченаведеною умовою.
lim T(n) ≤ c×f(n)
n→∞
Рівняння читається так, як n наближається до нескінченності, T n, менше або дорівнює c разів f n.
У великих O позначеннях це написано як
T(n)∈O(n)
Це читається, коли T з n знаходиться у великому O n.
Повернутися до англійської мови
Виходячи з математичного визначення вище, якщо ви говорите, що ваш алгоритм є великим O n, це означає, що це функція n (кількість вхідних параметрів) або швидше . Якщо ваш алгоритм є великим O n, то він також автоматично є великим O квадрата n.
Великий з n означає, що мій алгоритм працює як мінімум так швидко, як це. Ви не можете подивитися на велике позначення вашого алгоритму і сказати його повільно. Ви можете сказати лише її швидку.
Ознайомтеся з цим відео-підручником на Big O від UC Berkley. Це насправді проста концепція. Якщо ви почуєте, як професор Шевчук (він же вчитель рівня Бога) пояснює це, ви скажете "О, це все!".
Я знайшов дійсно чудове пояснення щодо великої нотації O, особливо для того, хто мало займається математикою.
https://rob-bell.net/2009/06/a-beginners-guide-to-big-o-notation/
Нотація Big O використовується в обчислювальній техніці для опису продуктивності або складності алгоритму. Big O спеціально описує найгірший сценарій і може бути використаний для опису потрібного часу виконання або простору, який використовується (наприклад, в пам'яті або на диску) алгоритмом.
Кожен, хто читає програмування Pearls або будь-які інші книги з інформатики та не має заземлення в математиці, потрапить у стіну, коли дійшов до глав, де згадується O (N log N) або інший, здавалося б, божевільний синтаксис. Сподіваємось, ця стаття допоможе вам зрозуміти основи великого O та логарифмів.
Як перший програміст і математик другий (а може, третій чи четвертий), я знайшов найкращий спосіб зрозуміти Біг О, щоб створити кілька прикладів у коді. Отже, нижче наведено декілька загальних порядків зростання, а також описи та приклади, де це можливо.
O (1)
O (1) описує алгоритм, який завжди буде виконуватися в один і той же час (або простір) незалежно від розміру набору вхідних даних.
bool IsFirstElementNull(IList<string> elements) { return elements[0] == null; }
O (N)
O (N) описує алгоритм, продуктивність якого буде рости лінійно і прямо пропорційно розміру набору вхідних даних. Наведений нижче приклад також демонструє, як Big O надає перевагу найгіршому сценарію ефективності; відповідна рядок може бути знайдена під час будь-якої ітерації циклу for і функція повертається рано, але позначення Big O завжди прийматимуть верхню межу, де алгоритм буде виконувати максимальну кількість ітерацій.
bool ContainsValue(IList<string> elements, string value) { foreach (var element in elements) { if (element == value) return true; } return false; }
O (N 2 )
O (N 2 ) являє собою алгоритм, продуктивність якого прямо пропорційна квадрату розміру вхідного набору даних. Це властиво алгоритмам, які передбачають вкладені ітерації над набором даних. Більш глибокі вкладені ітерації приведуть до O (N 3 ), O (N 4 ) тощо.
bool ContainsDuplicates(IList<string> elements) { for (var outer = 0; outer < elements.Count; outer++) { for (var inner = 0; inner < elements.Count; inner++) { // Don't compare with self if (outer == inner) continue; if (elements[outer] == elements[inner]) return true; } } return false; }
O (2 N )
O (2 N ) позначає алгоритм, зростання якого подвоюється з кожним доповненням до набору вхідних даних. Крива росту функції O (2 N ) є експоненціальною - починаючи дуже дрібно, потім метеорично піднімаючись. Прикладом функції O (2 N ) є рекурсивний розрахунок чисел Фібоначчі:
int Fibonacci(int number) { if (number <= 1) return number; return Fibonacci(number - 2) + Fibonacci(number - 1); }
Логарифми
Логарифми трохи складніші для пояснення, тому я використаю загальний приклад:
Двійковий пошук - це техніка, яка використовується для пошуку відсортованих наборів даних. Він працює, вибираючи середній елемент набору даних, по суті, медіану, і порівнює його з цільовим значенням. Якщо значення збігаються, це поверне успіх. Якщо цільове значення перевищує значення елемента зонда, воно займе верхню половину набору даних і буде виконувати ту саму операцію. Аналогічно, якщо цільове значення буде нижчим за значення елемента зонда, воно виконає операцію проти нижньої половини. Він буде продовжувати вдвічі зменшувати набір даних з кожною ітерацією, поки значення не буде знайдено або поки він не зможе більше розділити набір даних.
Цей тип алгоритму описується як O (log N). Ітеративна половина наборів даних, описана в прикладі двійкового пошуку, створює криву зростання, яка досягає максимуму на початку і повільно вирівнюється в міру збільшення розмірів наборів даних, наприклад, набір даних, що містить 10 елементів, займає одну секунду для завершення, набір даних що містить 100 елементів займає дві секунди, а набір даних, що містить 1000 елементів, займе три секунди. Подвоєння розміру набору вхідних даних мало впливає на його зростання, оскільки після одноразової ітерації алгоритму набір даних зменшиться вдвічі і, отже, нарівні з вхідними наборами даних наполовину менше. Це робить алгоритми на зразок двійкового пошуку надзвичайно ефективними при роботі з великими наборами даних.
Це дуже спрощене пояснення, але, сподіваюся, воно охоплює найважливіші деталі.
Скажімо, ваш алгоритм, що займається проблемою, залежить від деяких "факторів", наприклад, зробимо це N і X.
Залежно від N і X, ваш алгоритм потребуватиме деяких операцій, наприклад у випадку WORST - це 3(N^2) + log(X)
операції.
Оскільки Big-O не надто дбає про постійний коефіцієнт (ака 3), Big-O вашого алгоритму є O(N^2 + log(X))
. Це в основному означає "кількість операцій, які ваш алгоритм потребує для найгіршого масштабу".
алгоритм : процедура / формула вирішення задачі
Як аналізувати алгоритми та як можна порівняти алгоритми один проти одного?
приклад: вам і другові пропонується створити функцію для підсумовування чисел від 0 до N. Ви придумали f (x), і ваш друг придумає g (x). Обидві функції мають однаковий результат, але різний алгоритм. Для об'єктивного порівняння ефективності алгоритмів ми використовуємо нотацію Big-O .
Позначення Big-O: описує, як швидко зростатиме тривалість виконання відносно введення, оскільки вхід буде довільно великим.
3 ключових вивезення:
Складність простору: окрім часової складності, ми також дбаємо про складність простору (скільки пам'яті / простору використовує алгоритм). Замість перевірки часу операцій ми перевіряємо розмір розподілу пам’яті.