Що б спричинило складність алгоритму O (log n)?


106

Мої знання про big-O обмежені, і коли в журналі з’являються умови журналу, це відкидає мене ще більше.

Може хтось може просто пояснити мені, що таке O(log n)алгоритм? Звідки береться логарифм?

Це спеціально з’явилося, коли я намагався вирішити це питання середньої практики:

Нехай X (1..n) і Y (1..n) містять два списки цілих чисел, кожне відсортоване у не зменшуваному порядку. Дайте алгоритм O (log n) -time, щоб знайти медіану (або n-те найменше ціле число) всіх 2n об'єднаних елементів. Наприклад, X = (4, 5, 7, 8, 9) і Y = (3, 5, 8, 9, 10), тоді 7 є медіаною об'єднаного списку (3, 4, 5, 5, 7 , 8, 8, 9, 9, 10). [Підказка: використовувати поняття двійкового пошуку]


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

3
Цей веб-сайт допоміг мені udnerstand Big O notation: recursive-design.com/blog/2010/12/07/…
Бред

1
Мені цікаво, чому 7 є медіаною наведеного вище прикладу, fwiw це може бути і 8. Не такий хороший приклад?
стрийба

13
Хороший спосіб подумати про алгоритми O (log (n)) - це те, що на кожному кроці вони зменшують розмір проблеми вдвічі. Візьмемо приклад пошуку бінарних даних - на кожному кроці ви перевіряєте значення посередині діапазону пошуку, розділяючи діапазон навпіл; після цього ви вилучите одну із половинок із діапазону пошуку, а інша половина стане вашим діапазоном пошуку для наступного кроку. І тому на кожному кроці ваш діапазон пошуку вдвічі зменшується в розмірі, таким чином, O (log (n)) складність алгоритму. (зменшення не повинно бути рівно вдвічі, воно може бути на третину, на 25%, будь-який постійний відсоток; половина найчастіша)
Кшиштоф Козельчик

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

Відповіді:


290

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

Неодноразово ділиться постійною

Візьміть будь-яке число n; скажімо, 16. Скільки разів можна розділити n на два, перш ніж отримати число, менше або рівне одиниці? Для 16 ми це маємо

16 / 2 = 8
 8 / 2 = 4
 4 / 2 = 2
 2 / 2 = 1

Зауважте, що для цього потрібно виконати чотири кроки. Цікаво, що ми також маємо той журнал 2 16 = 4. Гммм ... а як щодо 128?

128 / 2 = 64
 64 / 2 = 32
 32 / 2 = 16
 16 / 2 = 8
  8 / 2 = 4
  4 / 2 = 2
  2 / 2 = 1

Це зробило сім кроків, а журнал 2 128 = 7. Це збіг? Ні! Для цього є вагомі причини. Припустимо, що ділимо число n на 2 i рази. Тоді отримуємо число n / 2 i . Якщо ми хочемо вирішити для значення i, де це значення не більше 1, ми отримаємо

n / 2 i ≤ 1

n ≤ 2 i

log 2 n ≤ i

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

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

То звідки це з'являється? Один класичний приклад - двійковий пошук , швидкий алгоритм пошуку відсортованого масиву для значення. Алгоритм працює так:

  • Якщо масив порожній, поверніть, що елемент не присутній у масиві.
  • Інакше:
    • Подивіться на середній елемент масиву.
    • Якщо вона дорівнює елементу, який ми шукаємо, поверніть успіх.
    • Якщо він більший за елемент, який ми шукаємо:
      • Викиньте другу половину масиву.
      • Повторіть
    • Якщо він менше елемента, який ми шукаємо:
      • Викиньте першу половину масиву.
      • Повторіть

Наприклад, для пошуку 5 в масиві

1   3   5   7   9   11   13

Ми спочатку подивимось на середній елемент:

1   3   5   7   9   11   13
            ^

Оскільки 7> 5, і оскільки масив відсортований, ми знаємо на факт, що число 5 не може бути в задній половині масиву, тому ми можемо просто відкинути його. Це листя

1   3   5

Отже, ми зараз розглянемо середній елемент:

1   3   5
    ^

Оскільки 3 <5, ми знаємо, що 5 не може з’явитися в першій половині масиву, тому ми можемо кинути перший масив, щоб залишити

        5

Знову ми дивимося на середину цього масиву:

        5
        ^

Оскільки це саме та кількість, яку ми шукаємо, ми можемо повідомити, що 5 справді є в масиві.

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

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

Обробка значень одна цифра за раз

Скільки цифр у базовій кількості 10 n? Що ж, якщо в цифрі є k цифр, то ми вважаємо, що найбільша цифра є кратною 10 k . Найбільше k-розрядне число - 999 ... 9, k разів, і це дорівнює 10 k + 1 - 1. Отже, якщо ми знаємо, що n має k цифр у ньому, то ми знаємо, що значення n дорівнює щонайбільше 10 k + 1 - 1. Якщо ми хочемо розв’язати для k з точки зору n, отримаємо

n ≤ 10 k + 1 - 1

n + 1 ≤ 10 k + 1

log 10 (n + 1) ≤ k + 1

(журнал 10 (n + 1)) - 1 ≤ k

З чого ми отримуємо, що k - приблизно логарифм n-10 з n. Іншими словами, кількість цифр у n дорівнює O (log n).

Наприклад, давайте подумаємо про складність додавання двох великих чисел, які занадто великі, щоб вписатись у машинне слово. Припустимо, що у нас є ці числа, представлені в базі 10, і будемо називати числа m і n. Один із способів їх додавання - це метод класної школи - випишіть цифри по одній цифрі, а потім працюйте справа наліво. Наприклад, щоб додати 1337 та 2065, ми б почали, записуючи числа як

    1  3  3  7
+   2  0  6  5
==============

Додаємо останню цифру і переносимо 1:

          1
    1  3  3  7
+   2  0  6  5
==============
             2

Потім додаємо другу до останньої ("передостанньої") цифру і переносимо 1:

       1  1
    1  3  3  7
+   2  0  6  5
==============
          0  2

Далі, ми додаємо третю до останньої ("antepenultimate") цифру:

       1  1
    1  3  3  7
+   2  0  6  5
==============
       4  0  2

Нарешті, ми додаємо четверту до останньої ("preantepenultimate" ... я люблю англійську) цифру:

       1  1
    1  3  3  7
+   2  0  6  5
==============
    3  4  0  2

Тепер, скільки ми зробили? Ми робимо загалом O (1) роботу на одну цифру (тобто постійну кількість роботи), і є O (max {log n, log m}) загальних цифр, які потрібно обробити. Це дає загальну складність O (max {log n, log m}), оскільки нам потрібно відвідати кожну цифру в двох числах.

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


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

Сподіваюся, це допомагає!


8

Коли ми говоримо про великі О-описи, ми зазвичай говоримо про час, який потрібно для вирішення задач заданого розміру . І зазвичай для простих задач цей розмір просто характеризується кількістю вхідних елементів, і це зазвичай називається n, або N. (Очевидно, що це не завжди так - проблеми з графіками часто характеризуються числом вершин, V і кількість ребер, E; але зараз ми поговоримо про списки об'єктів, з N об’єктів у списках.)

Ми говоримо, що проблема "велика-О (деяка функція N)", якщо і лише тоді :

Для всіх N> деяких довільних N_0 існує деяка константа c, така що час виконання алгоритму менше, ніж постійний c разів (деяка функція N.)

Іншими словами, не думайте про невеликі проблеми, де важлива «постійна накладність» розробки проблеми, а думати про великі проблеми. А коли думаєш про великі проблеми, великий-Oh of (деяка функція N) означає, що час роботи все ще завжди менше, ніж деякий постійний час, який функціонує. Завжди.

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

Отже, "big-Oh of log (n)" означає те саме, що я сказав вище, за винятком того, що "деяка функція N" замінюється на "log (n)".

Отже, ваша проблема спонукає вас подумати над бінарним пошуком, тому давайте подумаємо над цим. Припустимо, у вас є, скажімо, список з N елементів, відсортованих у порядку зростання. Ви хочете дізнатися, чи існує якесь вказане число у цьому списку. Один із способів зробити те, що є не є двійковим пошуком, - це просто сканувати кожен елемент списку і побачити, чи це ваш цільовий номер. Можливо, вам пощастить і знайдете його з першої спроби. Але в гіршому випадку ви перевірите N різних разів. Це не бінарний пошук, і він не великий-Oh of log (N), тому що немає способу примусити його до критеріїв, які ми накреслили вище.

Ви можете вибрати цю довільну константу c = 10, і якщо ваш список містить N = 32 елементів, ви добре: 10 * log (32) = 50, що більше, ніж час виконання 32. Але якщо N = 64 , 10 * log (64) = 60, що менше, ніж час виконання 64. Ви можете вибрати c = 100 або 1000 або gazillion, і ви все одно зможете знайти деякий N, який порушує цю вимогу. Іншими словами, немає N_0.

Якщо ми робимо двійковий пошук, ми вибираємо середній елемент і проводимо порівняння. Потім викидаємо половину чисел, і робимо це знову, і знову, і так далі. Якщо ваш N = 32, ви можете зробити це лише приблизно 5 разів, що є журналом (32). Якщо ваш N = 64, ви можете зробити це лише близько 6 разів і т. Д. Тепер ви можете вибрати цю довільну константу c таким чином, щоб вимога завжди відповідала великим значенням N.

З урахуванням цього фону, що O (log (N)) зазвичай означає, що у вас є якийсь спосіб зробити просту річ, яка скоротить розмір проблеми навпіл. Так само, як і двійковий пошук вище. Як тільки ви виріжете проблему навпіл, ви можете розрізати її навпіл знову, і знову, і знову. Але, що важливо, те, що ви не можете зробити, це певний крок попередньої обробки, який би зайняв більше часу, ніж цей час O (log (N)). Так, наприклад, ви не можете перетасувати свої два списки в один великий список, якщо ви також не знайдете способу зробити це в час O (log (N)).

(ПРИМІТКА. Практично завжди Log (N) означає log-base-two, що я припускаю вище.)


4

У наступному рішенні всі лінії з рекурсивним викликом виконуються на половині заданих розмірів підмасивів X і Y. Інші лінії виконуються в постійний час. Рекурсивна функція - T (2n) = T (2n / 2) + c = T (n) + c = O (lg (2n)) = O (lgn).

Ви починаєте з MEDIAN (X, 1, n, Y, 1, n).

MEDIAN(X, p, r, Y, i, k) 
if X[r]<Y[i]
    return X[r]
if Y[k]<X[p]
    return Y[k]
q=floor((p+r)/2)
j=floor((i+k)/2)
if r-p+1 is even
    if X[q+1]>Y[j] and Y[j+1]>X[q]
        if X[q]>Y[j]
            return X[q]
        else
            return Y[j]
    if X[q+1]<Y[j-1]
        return MEDIAN(X, q+1, r, Y, i, j)
    else
        return MEDIAN(X, p, q, Y, j+1, k)
else
    if X[q]>Y[j] and Y[j+1]>X[q-1]
        return Y[j]
    if Y[j]>X[q] and X[q+1]>Y[j-1]
        return X[q]
    if X[q+1]<Y[j-1]
        return MEDIAN(X, q, r, Y, i, j)
    else
        return MEDIAN(X, p, q, Y, j, k)

3

Термін Log дуже часто з'являється в аналізі складності алгоритму. Ось кілька пояснень:

1. Як ви представляєте число?

Давайте візьмемо число X = 245436. Це позначення "245436" містить в собі неявну інформацію. Зробити цю інформацію явною:

X = 2 * 10 ^ 5 + 4 * 10 ^ 4 + 5 * 10 ^ 3 + 4 * 10 ^ 2 + 3 * 10 ^ 1 + 6 * 10 ^ 0

Яке десяткове розширення числа. Отже, мінімальна кількість інформації, яка нам потрібна для представлення цього числа, - це 6 цифр. Це не випадково, оскільки будь-яке число менше 10 ^ d може бути представлене d цифрами.

Отже, скільки цифр потрібно для представлення X? То дорівнює найбільшому показнику 10 в X плюс 1.

==> 10 ^ d> X
==> log (10 ^ d)> log (X)
==> d * log (10)> log (X)
==> d> log (X) // І з'являється журнал знову ...
==> d = підлога (log (x)) + 1

Також зауважте, що це найбільш стислий спосіб позначення числа в цьому діапазоні. Будь-яке зменшення призведе до втрати інформації, оскільки пропущена цифра може бути відображена до 10 інших чисел. Наприклад: 12 * можна відобразити на 120, 121, 122,…, 129.

2. Як шукати число в (0, N - 1)?

Беручи N = 10 ^ d, ми використовуємо наше найважливіше спостереження:

Мінімальна кількість інформації для однозначної ідентифікації значення в діапазоні від 0 до N - 1 = цифр log (N).

Це означає, що, коли запитують шукати число в цілому рядку, що становить від 0 до N - 1, нам потрібно хоча б журнал (N) намагається його знайти. Чому? Будь-якому алгоритму пошуку потрібно буде вибрати одну цифру за іншою під час пошуку числа.

Мінімальна кількість цифр, яку йому потрібно вибрати, - це журнал (N). Отже, мінімальна кількість операцій, проведених для пошуку числа в просторі розміром N, - log (N).

Чи можете ви здогадатися про складність замовлення у двійковому пошуку, потрійному пошуку або дека-пошуку?
Його O (log (N))!

3. Як ви сортуєте набір чисел?

На запитання сортувати набір чисел A у масив B, ось як це виглядає ->

Пермутні елементи

Кожен елемент оригінального масиву повинен бути відображений у відповідному індексі в відсортованому масиві. Отже, для першого елемента ми маємо n позицій. Щоб правильно знайти відповідний індекс у цьому діапазоні від 0 до n - 1, нам потрібні… log (n) операції.

Наступному елементу потрібні операції журналу (n-1), наступний журнал (n-2) тощо. Загальна сума:

==> log (n) + log (n - 1) + log (n - 2) +… + log (1)

Використання log (a) + log (b) = log (a * b),

==> log (п!)

Це можна наблизити до nlog (n) - n.
Що таке O (n * log (n))!

Отже ми робимо висновок, що алгоритм сортування не може бути кращим, ніж O (n * log (n)). А деякі алгоритми, що мають таку складність, є популярними сортуванням об'єднань та сортуванням купи!

Ось деякі причини, чому ми бачимо, що журнал (n) так часто з’являється в аналізі складності алгоритмів. Те саме можна поширити і на двійкові числа. Я зробив відео про це тут.
Чому log (n) з'являється так часто під час аналізу складності алгоритму?

Ура!


2

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


1

Ще не можу коментувати ... некро це! Відповідь Аві Коена невірна, спробуйте:

X = 1 3 4 5 8
Y = 2 5 6 7 9

Жодна з умов не відповідає дійсності, тому МЕДІАН (X, p, q, Y, j, k) скоротить обидві п'ятірки. Це не зменшувані послідовності, не всі значення відрізняються.

Спробуйте також цей приклад парної довжини з різними значеннями:

X = 1 3 4 7
Y = 2 5 6 8

Тепер МЕДІАН (X, p, q, Y, j + 1, k) скоротить чотири.

Замість цього я пропоную цей алгоритм, називаємо його за допомогою MEDIAN (1, n, 1, n):

MEDIAN(startx, endx, starty, endy){
  if (startx == endx)
    return min(X[startx], y[starty])
  odd = (startx + endx) % 2     //0 if even, 1 if odd
  m = (startx+endx - odd)/2
  n = (starty+endy - odd)/2
  x = X[m]
  y = Y[n]
  if x == y
    //then there are n-2{+1} total elements smaller than or equal to both x and y
    //so this value is the nth smallest
    //we have found the median.
    return x
  if (x < y)
    //if we remove some numbers smaller then the median,
    //and remove the same amount of numbers bigger than the median,
    //the median will not change
    //we know the elements before x are smaller than the median,
    //and the elements after y are bigger than the median,
    //so we discard these and continue the search:
    return MEDIAN(m, endx, starty, n + 1 - odd)
  else  (x > y)
    return MEDIAN(startx, m + 1 - odd, n, endy)
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.