Це правильне "правило" для ідентифікації алгоритму "великого O"?


29

Я дізнався більше про нотацію Big O та як її обчислити на основі написання алгоритму. Я натрапив на цікавий набір "правил" для обчислення алгоритмів Big O notation і хотів дізнатися, чи я на правильному шляху чи не в дорозі.

Велика O Позначення: N

function(n) {
    For(var a = 0; i <= n; i++) { // It's N because it's just a single loop
        // Do stuff
    }
}

Велике позначення O: N 2

function(n, b) {
    For(var a = 0; a <= n; a++) {
        For(var c = 0; i <= b; c++) { // It's N squared because it's two nested loops
            // Do stuff
        }
    }
}

Велика нотація: 2N

function(n, b) {
    For(var a = 0; a <= n; a++) {
        // Do stuff
    }
    For(var c = 0; i <= b; c++) { // It's 2N the loops are outside each other
        // Do stuff
    }
}

Велика нотація: NLogN

function(n) {
    n.sort(); // The NLogN comes from the sort?
    For(var a = 0; i <= n; i++) {
        // Do stuff
    }
}

Чи правильні мої приклади та наступні позначення? Чи є додаткові позначення, про які я повинен знати?


3
Назвіть це як правило, а не формулу, і ви, мабуть, на правильному шляху. Звичайно, це повністю залежить від того, чим саме "займатися". Журнал (N) зазвичай походить від алгоритмів, які виконують певний бінарний / деревоподільний поділ. Ось відмінна публікація блогу на цю тему.
Даніель Б

15
Немає такого поняття, як 2Nу нотації big-O.
vartec

15
@ JörgWMittag тому, що O (2n) = O (n) за визначенням Big O
храповик з виродком

3
@ JörgWMittag: це справді не місце для тролінгу.
vartec

3
@vartec - Я не вірю, що JörgWMittag цілеспрямовано тролінгував. У своєму недавньому дослідженні я помітив велику плутанину між суворою нотацією Big-O і "загальною просторіччям", яка змішує Big-O, Theta та інші похідні. Я не кажу, що загальне використання правильне; тільки що це трапляється багато.

Відповіді:


26

Формально позначення big-O описує ступінь складності.

Для обчислення позначення big-O:

  1. визначити формулу складності алгоритму. Скажімо, наприклад, дві петлі, де ще одна вкладена всередину, потім ще три петлі не вкладені:2N² + 3N
  2. видалити все, крім найвищого терміна: 2N²
  3. видалити всі константи:

Іншими словами, дві петлі, ще одна вкладена всередину, тоді ще три петлі, які не вкладені, це O (N²)

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


Строго кажучи, "видалити всі константи" перетвориться 2N³на N. "видалити всі добавні та мультиплікативні константи" було б ближче до істини.
Йоахім Зауер

@JoachimSauer: N² = N * N, там немає постійної.
vartec

@vartec: згідно з тим же аргументом 2N = N+N.
Йоахім Зауер

2
@JoachimSauer, ваш "строго кажучи" як абсолютно нетрадиційний. Дивіться en.wikipedia.org/wiki/Constant_(mathematics) . Говорячи про поліноми, "константа" завжди посилається лише на коефіцієнти, а не на експоненти.
Бен Лі

1
@vartec, дивіться мій коментар вище. Ваше використання "константи" тут було абсолютно коректним та загальноприйнятим.
Бен Лі

6

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

Ось кілька прикладів із цим новим позначенням:

Для вашого першого прикладу лінійний обхід: це правильно!

O (N):

for (int i = 0; i < myArray.length; i++) {
    myArray[i] += 1;
}

Чому вона лінійна (O (n))? Коли ми додаємо додаткові елементи до вводу (масиву), кількість операцій, що відбувається, збільшується пропорційно кількості доданих нами елементів.

Отже, якщо потрібна одна операція, щоб збільшити ціле число десь у пам'яті, ми можемо моделювати роботу, яку робить цикл, за допомогою f (x) = 5x = 5 додаткових операцій. Для 20 додаткових елементів робимо 20 додаткових операцій. Один прохід масиву має тенденцію до лінійності. Так само такі алгоритми, як сортування відра, які здатні використовувати структуру даних, щоб зробити сортування за один прохід масиву.

Ваш другий приклад також був би правильним і виглядає так:

O (N ^ 2):

for (int i = 0; i < myArray.length; i++) {
    for (int j = 0; j < myArray.length; j++) {
        myArray[i][j] += 1;
    }
}

У цьому випадку для кожного додаткового елемента в першому масиві i, ми повинні обробити ВСЕ з j. Додавання 1 до i насправді додає (довжину j) до j. Таким чином, ви праві! Ця закономірність є O (n ^ 2), або в нашому прикладі це фактично O (i * j) (або n ^ 2, якщо i == j, що часто трапляється при матричних операціях або квадратній структурі даних.

Третій приклад - де все змінюється залежно від товару; Якщо код як написаний, а виконувати речі є постійною, це насправді лише O (n), оскільки у нас є 2 проходи масиву розміром n, а 2n зменшується до n. Петлі, що знаходяться поза однією стороною, не є ключовим фактором, який може створювати 2 ^ n код; ось приклад функції, яка дорівнює 2 ^ n:

var fibonacci = function (n) {
    if (n == 1 || n == 2) {
        return 1;
    }

    else {
        return (fibonacci(n-2) + fibonacci(n-1));
    }
}

Ця функція дорівнює 2 ^ n, оскільки кожен виклик функції виробляє ДВА додаткових виклику до функції (Фібоначчі). Щоразу, коли ми викликаємо функцію, кількість роботи, яку ми повинні виконати, подвоюється! Це дуже швидко росте, як відрізати голову від гідри і щоразу проростати два нові!

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

У JavaScript (як мінімум у Firefox) сортування за замовчуванням у Array.prototype.sort () дійсно є MergeSort, тому ви дивитесь на O (nlgn) для свого остаточного сценарію.


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

1

Вашим другим прикладом (зовнішня петля від 0 до n , внутрішня петля від 0 до b ) буде O ( nb ), а не O ( n 2 ). Правило полягає в тому, що ви обчислюєте щось n разів, і для кожного з них ви обчислюєте щось інше b разів, таким чином зростання цієї функції залежить виключно від зростання n * b .

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

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


0

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

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

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

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


-1

Великий о, за визначенням означає: для функції f (t) існує функція c * g (t), де c довільна константа така, що f (t) <= c * g (t) для t> n, де n є довільною постійною, тоді f (t) існує в O (g (t)). Це математичне позначення, яке використовується в інформатиці для аналізу алгоритмів. Якщо ваш розгублений я порекомендував би переглядати відносини закриття, то ви можете більш детально побачити, як ці алгоритми отримують ці великі величини.

Деякі наслідки цього визначення: O (n) насправді відповідає O (2n).

Також існує багато різних типів алгоритмів сортування. Мінімальне значення величини Big-Oh для порівняльного сортування - O (nlogn), однак існує безліч видів з гіршими великими-ох. Наприклад сортування виділення має O (n ^ 2). Деякі сортування порівняння можуть колись мати кращі великі величини. Наприклад, у відрі є O (n).

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