Я повинен погодитись, що це досить дивно, коли ви вперше бачите алгоритм 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). Багато вдосконалених алгоритмів, такі як алгоритм найкоротших шляхів Габова або масштабована версія алгоритму максимального потоку Форда-Фулкерсона , мають складний термін журналу, оскільки вони працюють однозначно.
Що стосується вашого другого питання про те, як ви вирішите цю проблему, ви, можливо, захочете поглянути на це пов'язане питання, яке вивчає більш просунуту програму. З огляду на загальну структуру проблем, що описані тут, тепер ви можете мати краще розуміння, як думати про проблеми, коли знаєте, що в результаті є термін журналу, тому я б радив не дивитись на відповідь, поки ви цього не надасте. деяка думка.
Сподіваюся, це допомагає!
O(log n)
може розглядатися як: Якщо ви подвоюєте розмір проблемиn
, ваш алгоритм потребує лише постійної кількості кроків більше.