Велике питання щодо алгоритму зі швидкістю (n ^ 2 + n) / 2


16

Я задаю це запитання, оскільки мене бентежить один аспект щодо великої нотації O.

Я використовую книгу, Структури даних та абстракції з Java Франком Каррано. У розділі "Ефективність алгоритмів" він показує такий алгоритм:

int sum = 0, i = 1, j = 1
for (i = 1 to n) {
    for (j = 1 to i)
        sum = sum + 1
}

Він спочатку описує цей алгоритм як швидкість росту (n 2  + n) / 2 . Який погляд на це здається інтуїтивним.

Однак потім зазначено, що (n 2  + n) / 2 поводиться як n 2, коли n великий. У тому ж абзаці він стверджує (п 2  + п) / 2 також веде себе так само, як п 2 / 2 . Він використовує це для класифікації вищевказаного алгоритму як O (n 2 ) .

Я отримую , що (п 2  + п) / 2 аналогічно п 2 / 2 , так як в процентному відношенні, п має невелике значення. Чого я не отримую - це чому (n 2  + n) / 2 і n 2 схожі, коли n велике.

Наприклад, якщо n = 1 000 000 :

(n^2 + n) / 2 =  500000500000 (5.000005e+11)
(n^2) / 2     =  500000000000 (5e+11)
(n^2)         = 1000000000000 (1e+12)

Цей останній зовсім не схожий. Насправді цілком очевидно, що це вдвічі більше, ніж середній. То як Френк Каррано може сказати, що вони схожі? Також, як класифікується алгоритм як O (n 2 ) . Дивлячись на цю внутрішню петлю, я б сказав, що це було n 2 + n / 2


Якщо вас цікавить, я дав відповідь на три вкладені петлі зі схемою перевірки дерева виконання Загадка, що стосується вкладених циклів
Grijesh Chauhan



1
в основному ідея полягає в тому, що в nміру зростання, як функції 'n ^ 2', так і ваша функція, поводяться аналогічно, вони мають постійну різницю в швидкості їх зростання. Якщо у вас складний вираз, функція, яка росте швидше, домінує.
AK_

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

Відповіді:


38

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

Якщо у вас є алгоритм зі складністю (n^2 + n)/2і ви подвоюєте кількість елементів, то константа 2не впливає на збільшення часу виконання, термін nвикликає подвоєння у часі виконання, а термін n^2спричиняє чотириразове збільшення виконання час.
Оскільки цей n^2термін має найбільший внесок, складність Big-O є O(n^2).


2
Мені це подобається, стає дещо зрозуміліше.
Andrew S

7
Це дуже хвилясто рукою. Може бути правдою чи може бути помилковою. Якщо ви можете взяти невелику кількість математики, дивіться одну з відповідей нижче.
usr

3
Це міркування занадто розпливчасте: це означало б, що ми могли б зробити висновок про це O(n * log n) = O(n), що не відповідає дійсності.
cfh

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

Барт справді говорив про терміни, а не про фактори. Розуміючи це, ми не можемо цього зробити O(n * log n) = O(n). Я думаю, що це дає гарне пояснення обґрунтування цього визначення.
Марк Фоскі

10

Визначення таке

f(n) = O(g(n))

якщо існує якась константа C> 0 така, що при всіх n більших, ніж деяких n_0, у нас є

|f(n)| <= C * |g(n)|

Це явно вірно для f (n) = n ^ 2 і g (n) = 1/2 n ^ 2, де константа C повинна бути 2. Також легко зрозуміти, що це правда для f (n) = n ^ 2 і g (n) = 1/2 (n ^ 2 + n).


4
"Якщо існує якась константа C> 0, така, forr all n", повинна бути "Якщо є деякі константи C, n_0 такі, що для всіх n> n_0"
Taemyr

@Taemyr: Поки функція gє ненульовою, це насправді не потрібно, оскільки ви завжди можете збільшувати константу C, щоб зробити заяву правдивим для кінцево багатьох перших n_0 значень.
cfh

Ні, якщо ми дивимось на функції, немає кінцевої кількості потенційних значень n_0.
Taemyr

@Taemyr: n_0 - кінцеве число. Виберіть C = max {f (i) / g (i): i = 1, ..., n_0}, і тоді оператор завжди буде містити перші n_0 значення, як ви можете легко перевірити.
cfh

У CS це не викликає занепокоєння, оскільки n зазвичай є вхідним розміром, а отже, і дискретно. У такому випадку можна вибрати C таким, що n_0 = 1 працює. Але формальне визначення є будь-яким n більшим, ніж деякий поріг, який знімає цілу масу випробовувань при застосуванні цього визначення.
Taemyr

6

Якщо говорити про складність, то вас цікавлять лише зміни фактору часу залежно від кількості елементів ( n).

Як такий, ви можете видалити будь-який постійний фактор (наприклад, 2тут).

Це залишає вас O(n^2 + n).

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

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


Наскільки великим повинен бути п, щоб різниця стала маргінальною? Крім того, чому вилучено / 2, його існування вдвічі перевищує значення?
Ендрю S

6
@AndrewS Тому, що Big O Notation говорить про зростання. Поділ на 2 не має значення поза контекстом орієнтирів та часових позначок, оскільки в кінцевому рахунку це не змінює темпи зростання. Однак найбільший компонент - це все.
Ніл

2
@Niel, блискуче так ясно. Мені б хотілося, щоб книги ставили це так. Іноді я думаю, що автори занадто багато знають, що вони забувають, що прості смертні не володіють своїми функціональними знаннями, і тому не роблять чітких важливих моментів, а натомість закопують це у якомусь формальному математичному описі або пропускають усе разом, вважаючи, що це мається на увазі.
Andrew S

Я б хотів, щоб я міг підтримати цю відповідь не раз! @Neil, ти повинен писати книги Big O.
Терсосаврос

3

Це не те, що «(n² + n) / 2 поводиться як n², коли n велике», це те, що (n² + n) / 2 росте як n² у міру збільшення n .

Наприклад, по мірі збільшення n з 1000 до 1 000 000

(n² + n) / 2  increases from  500500 to  500000500000
(n²) / 2      increases from  500000 to  500000000000
(n²)          increases from 1000000 to 1000000000000

Так само, як n збільшується з 1 000 000 000 000 000 000 000

(n² + n) / 2  increases from  500000500000 to  500000000500000000
(n²) / 2      increases from  500000000000 to  500000000000000000
(n²)          increases from 1000000000000 to 1000000000000000000

Вони ростуть аналогічно, саме про це йдеться у Big O Notation.

Якщо побудувати графік (n² + n) / 2 та n² / 2 на Wolfram Alpha , вони настільки схожі, що їх важко відрізнити на n = 100. Якщо побудувати всі три на Wolfram Alpha , ви побачите два рядки, розділені постійним коефіцієнтом 2.


Це добре, це дає мені дуже зрозуміло. Дякуємо за відповідь.
Андрій S

2

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

Як відомо, це позначення виражає асимптотичні порівняння функцій, а написання f = O (g) означає, що f (n) росте максимум так само швидко, як g (n), оскільки n переходить до нескінченності. Простий спосіб перекласти це - сказати, що функція f / g обмежена. Але, звичайно, ми повинні дбати про місця, де g дорівнює нулю, і ми закінчуємо більш чітке визначення, яке ви можете прочитати майже скрізь .

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

Тепер, щоб повернутися до вашого конкретного запитання, абсолютно марно обчислювати кілька числових значень і порівнювати їх: як би великий не був мільйон, він не враховує асимптотичну поведінку. Було б корисніше побудувати співвідношення функцій f (n) = n (n-1) / 2 і g (n) = n² - але в цьому спеціальному випадку ми можемо легко побачити, що f (n) / g (n) менше 1/2, якщо n> 0, що означає, що f = O (g) .

Щоб покращити своє розуміння позначення, слід

  • Робота з чітким визначенням, а не нечітким враженням на основі схожих речей - як ви це переживали, таке нечітке враження працює не дуже добре.

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


Алгебраїчна бічна примітка Якщо A - алгебра всіх функцій Ν → Ν і C субалгебра обмежених функцій, задана функція f, безліч функцій, що належать O (f), є C- підмодулем A , а правила обчислення великого O-позначення просто описують, як A працює на цих підмодулях. Таким чином, рівність, яку ми бачимо, - це рівність C- підмодулей A , це просто ще один різновид модуля.


1
Цю статтю у Вікіпедії важко прослідкувати після першої невеликої уваги. Він був написаний для досвідчених математиків досвідченим математиком і не є тим видом вступного тексту, якого я б очікував від енциклопедичної статті. Дякую за ваше розуміння, хоча все це добре.
Ендрю S

Ви завищуєте рівень у тексті Вікіпедії! :) Напевно, це не так добре написано. Грем, Кнут та Паташник написали прекрасну книгу «Конкретна математика» для студентів КС. Ви також можете спробувати "Мистецтво комп’ютерного програмування" або книгу з теорією чисел, написану в 50-х роках (Харді і Райт, Роза), оскільки вони зазвичай націлені на рівень учнів середньої школи. Вам не потрібно читати повну книгу, якщо ви виберете одну, просто частину про асимптотику! Але перш, ніж вам потрібно вирішити, скільки потрібно зрозуміти. :)
Майкл Ле Барб'є Грюневальд

1

Я думаю, ви неправильно розумієте, що означає велика нотація O.

Коли ви бачите O (N ^ 2), це в основному означає: коли проблема стає в 10 разів більшою, час її вирішення буде: 10 ^ 2 = 100 разів більшим.

Давайте пробиємо 1000 та 10000 у вашому рівнянні: 1000: (1000 ^ 2 + 1000) / 2 = 500500 10000: (10000 ^ 2 + 10000) / 2 = 50005000

50005000/500500 = 99,91

Отже, хоча N отримало в 10 разів більше, рішення отримали в 100 разів більше. Отже, він поводиться: O (N ^ 2)


1

якщо n був 1,000,000тоді

(n^2 + n) / 2  =  500000500000  (5.00001E+11)
(n^2) / 2      =  500000000000  (5E+11)
(n^2)          = 1000000000000  (1E+12)

1000000000000.00 що?

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

Це дає нам ступінь пропорції.

Якщо алгоритм повинен зробити щось n² разів, то знадобиться n² × c для деякого значення c, скільки часу займає кожна ітерація.

Якщо алгоритм повинен щось робити n² ÷ 2 рази, то знадобиться n² × c для деякого значення c, що вдвічі більше, ніж займає кожна ітерація.

Так чи інакше, час, який потрібно, все ще пропорційний n².

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

Але вони не те, про що говорить нам складність.

На практиці існує кілька різних способів покращити продуктивність алгоритму:

  1. Підвищення ефективності кожної ітерації: O (n²) все ще працює протягом n² × c секунд, але c менший.
  2. Зменшіть кількість спостережуваних випадків: O (n²) все ще працює за n² × c секунд, але n менше.
  3. Замініть алгоритм таким, який має однакові результати, але меншу складність: Наприклад, якщо ми могли б відновити щось O (n²) на щось O (n log n) і, отже, змінити з n² × c₀ секунд на (n log n) × c₁ секунд .

Або, щоб подивитися на це іншим способом, у нас f(n)×cзайняті секунди, і ви можете покращити продуктивність, зменшуючи c, зменшуючи nабо зменшуючи fприбуток за дану n.

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

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

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

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


"O (n!) Те саме, що O (1) для досить низьких значень n" просто помилково. Має бути кращий спосіб пояснити, що "коли nйого тримають досить низько, Big-O не має значення".
Бен Войгт

@BenVoigt Я ще не стикався з таким риторичним впливом, як це було, коли я вперше його прочитав; це спочатку не моє, я його вкрав у Еріка Ліпперта, який, можливо, походить з нього або, можливо, взяв його у когось іншого. Звичайно, він посилається на жарти, такі як "π дорівнює 3 для малих значень π та великих значень 3", які старіші.
Джон Ханна

0

Постійний фактор

Суть великої нотації O полягає в тому, що ви можете вибрати довільно великий постійний коефіцієнт, щоб O (функція (n)) завжди більше, ніж функція C * (n). Якщо алгоритм A в мільярд разів повільніший, ніж алгоритм B, то вони мають таку ж складність O, якщо ця різниця не зростає, коли n зростає довільно великим.

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

(n ^ 2 + n) / 2 "вписується всередину" O (n ^ 2), тому що для будь-якого n, незалежно від величини, (n ^ 2 + n) / 2 <1000000 * n ^ 2.

(n ^ 2 + n) / 2 "не відповідає" менший набір, наприклад O (n), оскільки для деяких значень (n ^ 2 + n) / 2> 1000000 * n.

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


0

Big-O - це все про те, як складний алгоритм. Якщо у вас є два алгоритму, і один займають n^2*kсекунди для запуску, а інші займають n^2*jсекунди , щоб бігти, то ви можете сперечатися про те, який з них краще, і ви могли б зробити деякі цікаві оптимізації , щоб спробувати вплинути kабо j, але обидва ці алгоритми мертві повільно порівняно з алгоритмом, який потрібно n*mзапустити. Не має значення, наскільки мало ви робите константи, kабо j, при досить великому введенні n*mалгоритм завжди виграє, навіть якщо mвін досить великий.

Отже, ми називаємо перші два алгоритми O(n^2), а другий - O(n). Це добре розділяє світ на класи алгоритмів. Це те, про що йдеться у big-O. Це як поділ транспортних засобів на автомобілі та вантажівки та автобуси тощо ... Існує велика різниця між автомобілями, і ви можете витратити цілий день, сперечаючись про те, чи краще Prius, ніж Chevy Volt, але в кінці дня, якщо ви потрібно поставити 12 людей в одного, тоді це досить безглуздий аргумент. :)

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