Вивчайте тему «Алгоритми та складність» з нуля
1Що вимірює складність
Алгоритм — це скінченний перелік точних кроків, що розв’язує задачу, наприклад сортування списку чи пошук прізвища в телефонній книзі.
Два алгоритми можуть розв’язувати ту саму задачу, але витрачати дуже різний час. Складність описує, як обсяг роботи зростає, коли вхідні дані збільшуються.
Розмір входу позначають n: кількість елементів для сортування, довжина списку, кількість вузлів графа.
Уявіть пошук прізвища в телефонній книзі на 1000 сторінок. Прочитати кожну сторінку — до 1000 кроків. Відкрити посередині й щоразу ділити навпіл — близько 10 кроків.
Часова складність рахує кроки. Просторова складність рахує додаткову пам’ять. Обидві записують як функцію від n.
Зазвичай розглядають найгірший випадок: найбільший обсяг роботи серед усіх входів розміру n. Іноді питають про середній або найкращий випадок.
Самоперевірка Алгоритм, обсяг роботи якого зростає як n², виконується 1 секунду для n = 1000. Скільки приблизно триватиме виконання для n = 2000?
- 1 секунда
- 2 секунди
- 4 секунди
- 8 секунд
Відповідь: 4 секунди. Подвоєння n множить n² на 2² = 4, тож час зростає приблизно з 1 до 4 секунд. 2 секунди були б відповіддю для лінійного зростання, а 8 — для n³.
2Велике O і класи зростання
Позначення велике O зберігає лише те, що важливо для великих n. Ви відкидаєте сталі множники й залишаєте лише доданок, що зростає найшвидше.
3n² + 5n + 2 ⇒ O(n²)
Для n = 1000 доданок n² дорівнює 3 000 000, тоді як 5n — лише 5000. Менші доданки майже не змінюють суми.
Ці класи повторюються знову й знову, від найповільнішого до найшвидшого зростання:
- O(1), стала: читання елемента масиву за індексом.
- O(log n), логарифмічна: двійковий пошук, що кожним кроком зменшує задачу вдвічі.
- O(n), лінійна: один прохід по всіх елементах, наприклад пошук найбільшого значення.
- O(n log n): добрі алгоритми сортування, як-от сортування злиттям.
- O(n²), квадратична: два вкладені цикли по елементах, як-от сортування бульбашкою.
- O(2ⁿ), експоненційна: перебір усіх підмножин. Швидко стає непридатною.
Велике O — це верхня межа: робота зростає щонайбільше так швидко. Ω (велика омега) — нижня межа, а Θ (велика тета) — точна межа, і верхня, і нижня.
Основа логарифма у великому O не має значення, бо логарифми з різними основами відрізняються лише сталим множником.
Самоперевірка Яке велике O для 4n + 2n² + 100?
- O(100)
- O(2n²)
- O(n)
- O(n²)
Відповідь: O(n²). Для великих n домінує доданок n², а сталий множник 2 відкидають. O(n) залишає хибний доданок, а O(2n²) забуває відкинути сталу.
3Пошук, сортування і рекурсія
Кілька алгоритмів трапляються майже в кожному курсі. Вивчіть їхню складність як факти.
- Лінійний пошук перевіряє елементи по одному: O(n). Двійковий пошук потребує відсортованого масиву й щокроку ділить діапазон навпіл: O(log n).
- Сортування бульбашкою, вставками та вибором порівнюють і міняють елементи у вкладених циклах: O(n²). Сортування вставками на вже відсортованому масиві — O(n).
- Сортування злиттям ділить масив навпіл, сортує половини й зливає їх: O(n log n) у кожному випадку, з O(n) додаткової пам’яті.
- Швидке сортування розбиває навколо опорного елемента: O(n log n) у середньому, але O(n²) у найгіршому випадку, наприклад для відсортованого масиву з першим елементом як опорним.
- Жодне сортування, що лише порівнює елементи, не може перевершити Ω(n log n) у найгіршому випадку. Сортування підрахунком швидше, бо не порівнює.
Рекурсивна функція викликає сама себе на меншому вході, доки не досягне базового випадку, на який відповідає безпосередньо. Її час роботи записують як рекурентне співвідношення:
- T(n) = T(n/2) + O(1) дає O(log n), як двійковий пошук.
- T(n) = 2T(n/2) + O(n) дає O(n log n), як сортування злиттям.
- T(n) = T(n − 1) + O(n) дає O(n²).
- T(n) = 2T(n − 1) + O(1) дає O(2ⁿ).
Варто знати три ідеї проєктування. Розділяй і володарюй розбиває, розв’язує й поєднує. Жадібний алгоритм на кожному кроці обирає локально найкращий варіант. Динамічне програмування зберігає відповіді на перекривні підзадачі, щоб обчислювати їх один раз.
Самоперевірка Яка часова складність швидкого сортування в найгіршому випадку?
- O(log n)
- O(n log n)
- O(n)
- O(n²)
Відповідь: O(n²). За поганих опорних елементів кожне розбиття залишає n − 1 елементів з одного боку, що дає O(n²). O(n log n) — його середній випадок і найгірший випадок сортування злиттям.
4Метод крок за кроком
Щоб знайти часову складність фрагмента коду:
- Визначте, що таке n: розмір входу.
- Підрахуйте, скільки разів виконується кожен цикл. Цикл, що щоразу додає 1, виконується приблизно n разів. Цикл, що подвоює або ділить навпіл, виконується приблизно log₂ n разів.
- Вкладені цикли множать. Цикли один за одним додають.
- Для рекурсивної функції запишіть рекурентне співвідношення: скільки викликів, на якому розмірі, плюс робота на виклик.
- Залишіть доданок, що зростає найшвидше, і відкиньте сталі.
- Перевірте на малому числі, наприклад n = 8 або n = 1024.
Для теоретичного запитання спершу назвіть алгоритм або ідею, яку воно описує, потім пригадайте її складність і умови (відсортований вхід, невід’ємні ваги, перекривні підзадачі).
5Розв’язаний приклад
Простежте кожен крок. Спробуйте передбачити наступний, перш ніж його відкрити.
Розв’язаний прикладЯка часова складність цього коду? for i = 1 to n: (j = 1; while j < n: j = j × 2)
1n — це число, до якого виконуються цикли.
2i іде від 1 до n з кроком 1, тож зовнішній цикл виконується n разів.
3j набуває значень 1, 2, 4, 8, … і зупиняється, досягнувши n. Після k ітерацій j = 2k, тож цикл зупиняється приблизно після log₂ n ітерацій.
4Внутрішній цикл розташований усередині зовнішнього, тож кількості множаться.
n · log₂ n ⇒ O(n log n)
5Для n = 1024 внутрішній цикл виконується log₂ 1024 = 10 разів, тож тіло виконується 1024 · 10 = 10 240 разів. Це набагато менше за n² = 1 048 576.
6Типові помилки
- Множити цикли, що виконуються один за одним. n + n = 2n — це все ще O(n).
- Залишати сталі, як у O(2n) чи O(n/2). Обидва — O(n).
- Рахувати цикл подвоєння як n ітерацій. Він виконується приблизно log₂ n разів.
- Застосовувати двійковий пошук до невідсортованих даних або до зв’язного списку, де до середини не дістатися за один крок.
- Наводити середній випадок швидкого сортування, коли питають про найгірший.
- Думати, що велике O означає «точно». O(n²) — це верхня межа; Θ(n²) — точне зростання.
Самоперевірка Функція виконує цикл з n ітерацій, а потім окремо — ще один цикл з n ітерацій. Яка її часова складність?
- O(2n)
- O(log n)
- O(n)
- O(n²)
Відповідь: O(n). Цикли один за одним додаються: n + n = 2n, і стала відкидається, тож O(n). O(n²) було б правильним, лише якби один цикл був усередині іншого.
7Як виглядають екзаменаційні запитання
- Легкі: ключові терміни й факти. Складність двійкового пошуку, сортування злиттям чи доступу до масиву, значення O, Ω і Θ, що таке рекурсія.
- Середній: застосування понять. Складність фрагмента коду, рекурентне співвідношення для розв’язання, як змінюється час при подвоєнні n, який алгоритм підходить для задачі.
- Складний: випадки й аналіз. Основна теорема про рекурентні співвідношення, найгірші випадки швидкого сортування, жадібні алгоритми проти динамічного програмування та класи P, NP і NP-повні.
P містить задачі, які можна розв’язати за поліноміальний час. NP містить задачі, розв’язки яких можна перевірити за поліноміальний час. Кожна задача з P належить до NP; чи P = NP, досі невідомо.
Вивчайте крок за кроком у застосункуВивчайте крок за кроком у застосунку