Big O Notation - Як розрахувати складність алгоритмів
Вступ
Big O notation (Велика О нотація) - це математичний спосіб опису того, як час виконання або використання пам'яті алгоритму зростає зі збільшенням розміру вхідних даних. Це критично важливо для написання ефективного коду та успішного проходження технічних співбесід.
🧠 Інтуїтивне розуміння - Життєві аналогії
Перш ніж переходити до коду, давайте зрозуміємо Big O через звичайні життєві ситуації:
📖 Аналогія з пошуком у книзі
Уявіть, що ви шукаєте слово в різних типах книг:
O(1) - Константна складність
🎯 Пошук у словнику з закладками
Ви знаєте, що слово "Apple" на сторінці 15
Незалежно від того, 100 чи 10,000 сторінок у словнику -
ви одразу переходите на сторінку 15
O(log n) - Логарифмічна складність
📚 Пошук у відсортованому словнику
1. Відкриваєте книгу посередині
2. Якщо ваше слово має бути раніше - беріть ліву половину
3. Якщо пізніше - праву половину
4. Повторюєте з новою половиною
Кожен крок зменшує область пошуку вдвічі!
O(n) - Лінійна складність
📑 Пошук у невідсортованій книзі
Читаєте сторінку за сторінкою, доки не знайдете
Якщо книга в 2 рази більша - шукатимете в 2 рази довше
O(n²) - Квадратична складність
🔍 Порівняння кожного слова з кожним
Для кожного слова у книзі порівнюєте його з усіма іншими словами
Якщо в книзі 100 слів - зробите 10,000 порівнянь!
🏪 Аналогія з походом у магазин
O(1) - Ви знаєте, де лежить хліб, і йдете прямо туди
O(log n) - Питаєте працівника, він каже "2 поверх, ліва секція", потім "3 ряд"
O(n) - Обходите всі ряди підряд, доки не знайдете
O(n²) - Для кожного товару перевіряєте всі інші товари на схожість
🔢 Візуальне розуміння з маленькими числами
Давайте подивимося, як зростає час виконання для різних алгоритмів:
| Розмір масиву (n) | O(1) | O(log n) | O(n) | O(n log n) | O(n²) | O(2ⁿ) |
|---|---|---|---|---|---|---|
| 1 | 1 | 0 | 1 | 0 | 1 | 2 |
| 2 | 1 | 1 | 2 | 2 | 4 | 4 |
| 4 | 1 | 2 | 4 | 8 | 16 | 16 |
| 8 | 1 | 3 | 8 | 24 | 64 | 256 |
| 16 | 1 | 4 | 16 | 64 | 256 | 65,536 |
| 1000 | 1 | 10 | 1,000 | 10,000 | 1,000,000 | 💥 |
Що це означає?
- ✅ O(1): Завжди 1 операція - супер!
- ✅ O(log n): Навіть для 1000 елементів - лише 10 операцій
- ⚠️ O(n): 1000 елементів = 1000 операцій - прийнятно
- ⚠️ O(n log n): 1000 елементів = 10,000 операцій - ще ОК
- ❌ O(n²): 1000 елементів = 1,000,000 операцій - повільно!
- 💀 O(2ⁿ): Вже при 16 елементах = 65,536 операцій - жахливо!
Основні поняття
Що таке Big O?
Big O описує найгірший випадок роботи алгоритму. Воно показує верхню межу того, наскільки повільно може працювати ваш код при збільшенні даних.
// Приклад: пошук елемента в масиві
function findElement(arr, target) {
for (let i = 0; i < arr.length; i++) {
if (arr[i] === target) {
return i;
}
}
return -1;
}
// Найкращий випадок: O(1) - елемент на першій позиції
// Найгірший випадок: O(n) - елемент в кінці або відсутній
// Big O завжди описує найгірший випадок: O(n)
Типи складності
| Нотація | Назва | Опис | Приклад |
|---|---|---|---|
| O(1) | Константна | Час не залежить від розміру | Доступ до елемента масиву |
| O(log n) | Логарифмічна | Час зростає логарифмічно | Бінарний пошук |
| O(n) | Лінійна | Час пропорційний розміру | Ітерація по масиву |
| O(n log n) | Лінійно-логарифмічна | Комбінація лінійної та логарифмічної | Ефективне сортування |
| O(n²) | Квадратична | Час пропорційний квадрату розміру | Вкладені цикли |
| O(2ⁿ) | Експоненціальна | Час подвоюється з кожним новим елементом | Рекурсія Фібоначчі |
Як розрахувати Big O - Покрокова інструкція
Крок 1: Знайдіть домінуючі операції
function example1(arr) {
let sum = 0; // O(1)
for (let i = 0; i < arr.length; i++) { // O(n)
sum += arr[i]; // O(1)
}
console.log(sum); // O(1)
return sum; // O(1)
}
// Результат: O(1) + O(n) + O(1) + O(1) + O(1) = O(n)
// Домінує O(n), тому загальна складність O(n)
Крок 2: Ігноруйте константи
function example2(arr) {
// Перший цикл: O(n)
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
// Другий цикл: O(n)
for (let i = 0; i < arr.length; i++) {
console.log(arr[i] * 2);
}
}
// O(n) + O(n) = O(2n) = O(n)
// Константи ігноруються
Крок 3: Аналізуйте вкладені цикли
function example3(arr) {
for (let i = 0; i < arr.length; i++) { // O(n)
for (let j = 0; j < arr.length; j++) { // O(n)
console.log(arr[i], arr[j]); // O(1)
}
}
}
// O(n) * O(n) * O(1) = O(n²)
🤔 Метод "Думання вголос" - Як аналізувати крок за кроком
Давайте навчимося аналізувати код, думаючи вголос як справжній програміст:
Приклад 1: Простий код
function simple(arr) {
console.log(arr[0]); // ← Що тут відбувається?
console.log(arr[1]); // ← Що тут відбувається?
console.log("Hello"); // ← Що тут відбувається?
}
💭 Думання вголос:
"Хм, що тут відбувається? Дивлюся на кожен рядок:
- Рядок 1: Виводжу перший елемент - це завжди одна операція, незалежно від розміру масиву
- Рядок 2: Виводжу другий елемент - теж одна операція
- Рядок 3: Виводжу текст - одна операція
Всього: 3 операції. Якщо масив має 10 елементів - 3 операції. Якщо 1000 елементів - все одно 3 операції! Це означає O(1) - константна складність."
Приклад 2: Один цикл
function oneLoop(arr) {
for (let i = 0; i < arr.length; i++) { // ← Скільки раз це виконається?
console.log(arr[i]); // ← Що відбувається всередині?
}
}
💭 Думання вголос:
"Бачу цикл. Питаю себе: скільки раз він виконається?
- Цикл йде від 0 до arr.length
- Якщо масив має 5 елементів, цикл виконається 5 разів
- Якщо масив має 100 елементів, цикл виконається 100 разів
- Всередині циклу лише одна операція console.log
Висновок: кількість операцій = розмір масиву = n Це O(n) - лінійна складність"
Приклад 3: Два цикли підряд
function twoLoops(arr) {
// Перший цикл
for (let i = 0; i < arr.length; i++) {
console.log("First:", arr[i]);
}
// Другий цикл
for (let i = 0; i < arr.length; i++) {
console.log("Second:", arr[i]);
}
}
💭 Думання вголос:
"Два цикли, але вони один за одним, не вкладені.
- Перший цикл: n операцій
- Другий цикл: n операцій
- Разом: n + n = 2n операцій
Але в Big O ми відкидаємо константи! 2n = O(n). Два цикли підряд - все одно O(n)"
Приклад 4: Вкладені цикли
function nestedLoops(arr) {
for (let i = 0; i < arr.length; i++) { // ← Зовнішній цикл
for (let j = 0; j < arr.length; j++) { // ← Внутрішній цикл
console.log(arr[i], arr[j]); // ← Що тут?
}
}
}
💭 Думання вголос:
"Ось тут цікаво! Цикл всередині циклу.
- Зовнішній цикл виконується n разів
- Для КОЖНОЇ ітерації зовнішнього циклу, внутрішній цикл виконується n разів
- Якщо масив має 3 елементи:
- i=0: внутрішній цикл 3 рази (j=0,1,2)
- i=1: внутрішній цикл 3 рази (j=0,1,2)
- i=2: внутрішній цикл 3 рази (j=0,1,2)
- Всього: 3 × 3 = 9 операцій
Загальна формула: n × n = n² Це O(n²) - квадратична складність"
🎯 Простий алгоритм розпізнавання
Ось простий спосіб швидко визначити складність:
🔍 Крок 1: Рахуйте цикли
// Немає циклів → O(1)
function constant() {
return 5 + 3;
}
// Один цикл → O(n)
function linear(arr) {
for (item of arr) { }
}
// Два цикли підряд → O(n)
function stillLinear(arr) {
for (item of arr) { } // n операцій
for (item of arr) { } // + n операцій = 2n = O(n)
}
// Цикл в циклі → O(n²)
function quadratic(arr) {
for (item1 of arr) { // n разів
for (item2 of arr) { // × n разів = n²
}
}
}
// Три вкладені цикли → O(n³)
function cubic(arr) {
for (item1 of arr) {
for (item2 of arr) {
for (item3 of arr) {
}
}
}
}
🔍 Крок 2: Ділення навпіл = log n
// Якщо на кожному кроці ділите задачу навпіл → O(log n)
function binarySearch() {
// Кожна ітерація: розмір задачі / 2
// n → n/2 → n/4 → n/8 → ... → 1
}
🔍 Крок 3: Рекурсія з двома викликами = 2ⁿ
// Якщо кожен виклик породжує 2 нових → O(2ⁿ)
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n-1) + fibonacci(n-2); // 2 виклики!
}
📝 Тренування: Розберіть самостійно
Спробуйте визначити складність цих функцій:
Завдання 1:
function mystery1(arr) {
let sum = 0;
for (let i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
💡 Підказка
Один цикл від 0 до n✅ Відповідь
O(n) - лінійна складність. Один цикл, який виконується n разів.Завдання 2:
function mystery2(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] > arr[j]) {
console.log("Found pair");
}
}
}
}
💡 Підказка
Цикл в циклі, але внутрішній починається з i+1✅ Відповідь
O(n²) - квадратична складність. Хоча внутрішній цикл не завжди виконується n разів, у найгіршому випадку маємо приблизно n²/2 операцій, що дає O(n²).Завдання 3:
function mystery3(arr) {
if (arr.length <= 1) return arr;
const mid = Math.floor(arr.length / 2);
const left = mystery3(arr.slice(0, mid));
const right = mystery3(arr.slice(mid));
return merge(left, right); // merge() працює за O(n)
}
💡 Підказка
Ділимо масив навпіл на кожному рівні рекурсії✅ Відповідь
O(n log n). Рівнів рекурсії log n (ділимо навпіл), на кожному рівні робимо O(n) роботи.Практичні приклади з JavaScript
O(1) - Константна складність
// Доступ до елемента масиву
function getFirst(arr) {
return arr[0]; // Завжди O(1), незалежно від розміру масиву
}
// Додавання до кінця масиву
function addToEnd(arr, element) {
arr.push(element); // O(1) в середньому
}
// Доступ до властивості об'єкта
function getProperty(obj, key) {
return obj[key]; // O(1) для об'єктів та Map
}
O(log n) - Логарифмічна складність
// Бінарний пошук в відсортованому масиві
function binarySearch(arr, target) {
let left = 0;
let right = arr.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) {
return mid;
} else if (arr[mid] < target) {
left = mid + 1; // Відкидаємо половину масиву
} else {
right = mid - 1; // Відкидаємо половину масиву
}
}
return -1;
}
// На кожній ітерації розмір задачі зменшується вдвічі
// Тому складність O(log n)
O(n) - Лінійна складність
// Пошук максимального елемента
function findMax(arr) {
let max = arr[0];
for (let i = 1; i < arr.length; i++) { // O(n)
if (arr[i] > max) {
max = arr[i];
}
}
return max;
}
// Фільтрація масиву
function filterEven(arr) {
const result = [];
for (const num of arr) { // O(n)
if (num % 2 === 0) {
result.push(num); // O(1)
}
}
return result;
}
O(n log n) - Лінійно-логарифмічна складність
// Сортування злиттям (Merge Sort)
function mergeSort(arr) {
if (arr.length <= 1) {
return arr;
}
const mid = Math.floor(arr.length / 2);
const left = mergeSort(arr.slice(0, mid)); // O(log n) рівнів рекурсії
const right = mergeSort(arr.slice(mid)); // O(log n) рівнів рекурсії
return merge(left, right); // O(n) на кожному рівні
}
function merge(left, right) {
const result = [];
let i = 0, j = 0;
while (i < left.length && j < right.length) { // O(n)
if (left[i] <= right[j]) {
result.push(left[i++]);
} else {
result.push(right[j++]);
}
}
return result.concat(left.slice(i)).concat(right.slice(j));
}
// O(log n) рівнів * O(n) робота на рівень = O(n log n)
O(n²) - Квадратична складність
// Bubble Sort
function bubbleSort(arr) {
const n = arr.length;
for (let i = 0; i < n - 1; i++) { // O(n)
for (let j = 0; j < n - i - 1; j++) { // O(n)
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]; // O(1)
}
}
}
return arr;
}
// Пошук дублікатів (наївний підхід)
function findDuplicates(arr) {
const duplicates = [];
for (let i = 0; i < arr.length; i++) { // O(n)
for (let j = i + 1; j < arr.length; j++) { // O(n)
if (arr[i] === arr[j]) {
duplicates.push(arr[i]);
}
}
}
return duplicates;
}
O(2ⁿ) - Експоненціальна складність
// Наївна рекурсія Фібоначчі
function fibonacci(n) {
if (n <= 1) {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
// Кожен виклик породжує 2 нових виклики
// Глибина рекурсії n, тому O(2ⁿ)
}
// Оптимізована версія з мемоізацією O(n)
function fibonacciMemo(n, memo = {}) {
if (n <= 1) return n;
if (memo[n]) return memo[n];
memo[n] = fibonacciMemo(n - 1, memo) + fibonacciMemo(n - 2, memo);
return memo[n];
}
Просторова складність
Просторова складність описує, скільки додаткової пам'яті використовує алгоритм.
// O(1) просторова складність
function sumArray(arr) {
let sum = 0; // Константний об'єм пам'яті
for (const num of arr) {
sum += num;
}
return sum;
}
// O(n) просторова складність
function reverseArray(arr) {
const reversed = []; // Масив розміром n
for (let i = arr.length - 1; i >= 0; i--) {
reversed.push(arr[i]);
}
return reversed;
}
// O(log n) просторова складність (рекурсивний стек)
function binarySearchRecursive(arr, target, left = 0, right = arr.length - 1) {
if (left > right) return -1;
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) return mid;
if (arr[mid] < target) {
return binarySearchRecursive(arr, target, mid + 1, right);
}
return binarySearchRecursive(arr, target, left, mid - 1);
}
// Глибина рекурсії log n, кожен виклик займає O(1) пам'яті
Аналіз складних випадків
Приклад 1: Аналіз зі зменшенням розміру
function mysteryFunction(arr) {
if (arr.length <= 1) return arr;
const mid = Math.floor(arr.length / 2);
// Обробляємо тільки половину масиву
return mysteryFunction(arr.slice(0, mid));
}
// T(n) = T(n/2) + O(1)
// Це дає нам O(log n) часову складність
// Але slice() створює новий масив, тому O(n) просторова складність
Приклад 2: Амортизована складність
class DynamicArray {
constructor() {
this.data = new Array(1);
this.size = 0;
this.capacity = 1;
}
push(element) {
if (this.size === this.capacity) {
this.resize(); // O(n) в найгіршому випадку
}
this.data[this.size] = element; // O(1)
this.size++;
}
resize() {
const newCapacity = this.capacity * 2;
const newData = new Array(newCapacity);
for (let i = 0; i < this.size; i++) {
newData[i] = this.data[i];
}
this.data = newData;
this.capacity = newCapacity;
}
}
// Поодинокий push(): O(1) амортизована складність
// Хоча іноді resize() дає O(n), це відбувається рідко
Поради для співбесід
1. Структурований підхід до аналізу
function analyzeComplexity(func) {
// 1. Визначте базові операції
// 2. Знайдіть цикли та рекурсію
// 3. Проаналізуйте, як зростає робота з розміром даних
// 4. Врахуйте найгірший випадок
// 5. Спростіть, прибравши константи
}
2. Покрокове пояснення
// Поганий приклад пояснення:
"Це O(n²) бо є два цикли"
// Хороший приклад пояснення:
"Зовнішній цикл виконується n разів. Для кожної ітерації зовнішнього циклу,
внутрішній цикл також виконується до n разів. У найгіршому випадку
ми виконуємо n * n = n² операцій, тому складність O(n²)"
3. Практичні поради
// Завжди запитуйте про обмеження:
function optimizeForConstraints(arr) {
const n = arr.length;
// Якщо n < 100: можна використати O(n²) алгоритм
if (n < 100) {
return bubbleSort(arr);
}
// Якщо n велике: потрібен O(n log n) алгоритм
return mergeSort(arr);
}
Поширені помилки та пастки
Помилка 1: Плутанина з вбудованими методами
// НЕПРАВИЛЬНО: вважати що це O(n)
function badExample(arr) {
for (const item of arr) { // O(n)
if (arr.includes(item * 2)) { // includes() це O(n)!
console.log(item);
}
}
}
// Насправді це O(n²)!
// ПРАВИЛЬНО: використати Set для O(1) пошуку
function goodExample(arr) {
const set = new Set(arr); // O(n) для створення
for (const item of arr) { // O(n)
if (set.has(item * 2)) { // has() це O(1)
console.log(item);
}
}
}
// Це справді O(n)
Помилка 2: Ігнорування просторової складності
// Здається ефективним, але використовує O(n²) пам'яті
function createMatrix(n) {
const matrix = [];
for (let i = 0; i < n; i++) {
const row = [];
for (let j = 0; j < n; j++) {
row.push(i * j);
}
matrix.push(row);
}
return matrix; // O(n²) просторова складність
}
Помилка 3: Неврахування рекурсивного стеку
// Виглядає як O(n) часова складність
function recursiveSum(arr, index = 0) {
if (index >= arr.length) return 0;
return arr[index] + recursiveSum(arr, index + 1);
}
// Але просторова складність O(n) через стек викликів!
// Ітеративна версія мала б O(1) просторову складність
Практичні завдання для самоперевірки
Завдання 1: Визначте складність
function mystery1(arr) {
let count = 0;
for (let i = 0; i < arr.length; i++) {
for (let j = i; j < arr.length; j++) {
count++;
}
}
return count;
}
// Ваша відповідь: ?
// Правильна відповідь: O(n²)
// Пояснення: i змінюється від 0 до n, j від i до n
// Загальна кількість ітерацій: n + (n-1) + (n-2) + ... + 1 = n(n+1)/2 = O(n²)
Завдання 2: Оптимізуйте функцію
// Повільна версія O(n²)
function hasDuplicatesSlow(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] === arr[j]) {
return true;
}
}
}
return false;
}
// Швидка версія O(n)
function hasDuplicatesFast(arr) {
const seen = new Set();
for (const item of arr) {
if (seen.has(item)) {
return true;
}
seen.add(item);
}
return false;
}
Висновки
Big O notation - це фундаментальний інструмент для:
- Аналізу ефективності коду
- Порівняння алгоритмів
- Прогнозування продуктивності на великих даних
- Успішного проходження технічних співбесід
Ключові принципи:
- Аналізуйте найгірший випадок
- Ігноруйте константи та менші терми
- Враховуйте як часову, так і просторову складність
- Думайте про масштабованість
Порядок зростання (від кращого до гіршого):
O(1) < O(log n) < O(n) < O(n log n) < O(n²) < O(n³) < O(2ⁿ) < O(n!)
Пам'ятайте: розуміння Big O допомагає писати код, який залишається швидким навіть при зростанні обсягу даних. Це критично важливо для створення масштабованих додатків та успішного проходження технічних співбесід.
🚀 Швидкий чек-лист для початківців
✅ Перед аналізом коду запитайте себе:
- Чи є цикли?
- Немає → O(1)
- Один → O(n)
- Вкладені → O(n²), O(n³)...
- Чи ділиться задача навпіл?
- Так → O(log n)
- Чи є рекурсія з кількома викликами?
- Так → можливо O(2ⁿ) або O(n!)
- Які вбудовані методи використовуються?
arr.includes(),arr.indexOf()→ O(n)Set.has(),Map.get()→ O(1)arr.sort()→ O(n log n)
🎯 Швидкі шаблони розпізнавання:
// 🟢 O(1) - Константа
arr[0], obj.property, Math.max(a, b)
// 🟡 O(log n) - Логарифм
binarySearch, balanced tree operations
// 🟡 O(n) - Лінійна
for (item of arr), arr.find(), arr.filter()
// 🟠 O(n log n) - Квазілінійна
mergeSort, quickSort (середній випадок)
// 🔴 O(n²) - Квадратична
for + for (вкладені), bubbleSort
// 💀 O(2ⁿ) - Експоненціальна
fibonacci (наївна рекурсія)
🎮 Інтерактивні вправи для закріплення
Вправа 1: Гра "Швидке розпізнавання"
Подивіться на код та швидко скажіть складність:
// A)
function example(n) {
return n * 2;
}
// B)
function example(arr) {
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
}
// C)
function example(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < arr.length; j++) {
console.log(i, j);
}
}
}
// D)
function example(arr, target) {
let left = 0, right = arr.length - 1;
while (left <= right) {
let mid = Math.floor((left + right) / 2);
if (arr[mid] === target) return mid;
if (arr[mid] < target) left = mid + 1;
else right = mid - 1;
}
}
🎯 Відповіді
- A) O(1) - Одна операція незалежно від n
- B) O(n) - Один цикл по масиву
- C) O(n²) - Вкладені цикли
- D) O(log n) - Бінарний пошук (ділення навпіл)
Вправа 2: Знайдіть помилки в аналізі
function processData(arr) {
// Крок 1: Видалити дублікати
const unique = [];
for (let i = 0; i < arr.length; i++) {
if (!unique.includes(arr[i])) { // ⚠️ Помилка тут!
unique.push(arr[i]);
}
}
// Крок 2: Відсортувати
unique.sort(); // O(n log n)
return unique;
}
❌ Неправильний аналіз: "Це O(n log n) через сортування"
✅ Правильний аналіз: Це O(n²)!
includes()всередині циклу: O(n) × O(n) = O(n²)- Сортування: O(n log n)
- Домінує O(n²)
🔧 Як виправити:
function processDataOptimized(arr) {
// O(n) замість O(n²)
const unique = [...new Set(arr)];
// O(n log n)
unique.sort();
return unique;
}
// Загальна складність: O(n log n)
Вправа 3: Реальні сценарії
Яку складність мають ці операції у вашому додатку?
// Сценарій 1: Автокомпліт у пошуку
function autocomplete(query, dictionary) {
return dictionary.filter(word =>
word.startsWith(query) // O(k) де k - довжина слова
);
}
// Відповідь: O(n×k) де n - розмір словника, k - середня довжина слова
// Сценарій 2: Рендерінг списку
function renderUserList(users) {
return users.map(user => ({
...user, // O(p) де p - кількість властивостей
avatar: generateAvatar(user.id) // O(1)
}));
}
// Відповідь: O(n×p) де n - кількість користувачів
// Сценарій 3: Валідація форми
function validateForm(formData) {
const errors = {};
for (const field in formData) { // O(f) полів
if (formData[field].length === 0) {
errors[field] = "Required";
}
}
return errors;
}
// Відповідь: O(f) де f - кількість полів форми
🛠️ Практичні поради для розробки
1. Коли не варто хвилюватися про Big O
// ✅ Для невеликих даних (< 1000 елементів) O(n²) може бути OK
function smallDataSort(arr) {
if (arr.length < 100) {
return bubbleSort(arr); // O(n²) але простіший код
}
return mergeSort(arr); // O(n log n) для великих даних
}
// ✅ Одноразові операції
function initializeApp() {
// O(n²) при ініціалізації - не проблема
for (let config of configs) {
for (let plugin of plugins) {
if (plugin.supports(config)) {
plugin.configure(config);
}
}
}
}
2. Коли Big O критично важливо
// ❌ Це буде повільно для великих даних!
function searchInRealTime(query, largeDataset) {
return largeDataset.filter(item =>
item.title.includes(query) || // O(n×m)
item.description.includes(query) // O(n×m)
);
}
// ✅ Краще використати індекси
class SearchIndex {
constructor(dataset) {
this.index = new Map(); // Попередньо створений індекс
// Індексація: O(n×m) один раз при створенні
}
search(query) {
return this.index.get(query) || []; // O(1) пошук!
}
}
3. Швидкі перевірки в коді
// 🚨 Червоні прапорці - ці конструкції потребують уваги:
// Вкладені цикли
for (...) {
for (...) { // ⚠️ Можливо O(n²)
}
}
// Array методи в циклах
for (item of array) {
if (array.includes(something)) { // ⚠️ O(n²)
}
}
// Рекурсія без мемоізації
function recursive(n) {
return recursive(n-1) + recursive(n-2); // ⚠️ Можливо O(2ⁿ)
}
// Створення об'єктів в циклах
for (item of array) {
result.push({...item, extra: value}); // ⚠️ Копіювання може бути дорогим
}
🎓 Заключні поради
Запам'ятайте головне:
- Почніть з простого - рахуйте цикли
- Практикуйтеся щодня - аналізуйте код, який пишете
- Не паніикуйте - Big O стає інтуїтивним з часом
- Думайте про користувача - швидкий код = краща користувацька взаємодія
Коли сумніваєтесь:
- Спробуйте з маленькими числами (n=1,2,3,4)
- Подумайте: "що станеться, якщо даних стане в 10 разів більше?"
- Шукайте домінуючи операції (найповільніші частини)
Успіхів у вивченні алгоритмів! 🚀