Algorithms and Data Structures

Основи алгоритмів і структур даних у JavaScript

алгоритми javascript

🧠 Основи алгоритмів і структур даних у JavaScript: повний гайд для початківців і не тільки. Детальний вступ до алгоритмів і структур даних для веброзробників на JavaScript з практичними прикладами, поясненням Big O нотації, масивами, об’єктами, списками, стеком, чергами та іншими фундаментальними поняттями.


🔍 Що таке алгоритми і структури даних?

Алгоритм — це набір покрокових інструкцій для досягнення конкретної мети або вирішення задачі.
Структура даних — це спосіб організації, зберігання й обробки даних так, щоб алгоритми могли працювати ефективно.

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

Таким чином, алгоритми – це кроки по вирішенню задачі, структури даних – це способи роботи з даними для ефективної імплементації алгоритму.


🧮 Big O Notation — мова продуктивності алгоритмів

Big O — це математичний запис, що описує найгірший випадок роботи часу або пам’яті, які використовує алгоритм.

Ось нижче – структурована таблиця Big O — з максимально повним переліком найпоширеніших випадків роботи алгоритмів і колонкою їх ефективності від найефективніших до найповільніших:


📊 Таблиця Big O Notation: порівняння ефективності алгоритмів

Big OНазва (тип зростання)Приклад / ОписЕфективність
O(1)КонстантнаДоступ до елемента в масиві arr[0]🟢 Найкраща
O(log n)ЛогарифмічнаБінарний пошук в відсортованому масиві🟢 Дуже хороша
O(√n)Квадратний коріньАлгоритми для перевірки простих чисел🟢 Добра
O(n)ЛінійнаПрохід по масиву, обробка кожного елемента🟡 Середня
O(n log n)Лінійно-логарифмічнаСортування (MergeSort, QuickSort у середньому)🟡 Прийнятна
O(n²)КвадратичнаВкладені цикли: сортування бульбашкою, добутки матриць🔴 Повільна
O(n³)Кубічна3 вкладені цикли, обрахунки в графах🔴 Дуже повільна
O(2ⁿ)ЕкспоненціальнаГенерація всіх підмножин, рекурсивний Фібоначчі🔴 Неефективна
O(n!)ФакторіальнаПеребір всіх перестановок (наприклад, задачі типу комівояжера)🔴 Катастрофічна

💡 Пояснення ефективності:

  • 🟢 Ефективні (O(1), O(log n)) — ідеальні для великих обсягів даних.
  • 🟡 Середні (O(n), O(n log n)) — практично використовуються у більшості реальних алгоритмів.
  • 🔴 Повільні (O(n²) і гірше) — прийнятні тільки для малих обсягів даних або як винятки.

📌 Приклади для розуміння:

  • O(1): const first = arr[0];
  • O(n): for (let i = 0; i < arr.length; i++) console.log(arr[i]);
  • O(n²): for (let i = 0; i < arr.length; i++) { for (let j = 0; j < arr.length; j++) { console.log(arr[i], arr[j]); } }
  • O(2ⁿ): function fib(n) { if (n <= 1) return n; return fib(n - 1) + fib(n - 2); }

🔎 Знання Big O — це ключ до написання швидких, надійних та масштабованих алгоритмів. Завжди прагни уникати O(n²) і гірших складностей для великих обсягів даних.

Приклад O(n):

function printAll(arr) {
  for (let i = 0; i < arr.length; i++) {
    console.log(arr[i]);
  }
}

📦 Основні структури даних у JavaScript

1️⃣ Масиви (Array)

Масив — це впорядкована колекція елементів.

Приклади:

const fruits = ["apple", "banana", "cherry"];
console.log(fruits[1]); // "banana"

Типові методи:

  • push, pop
  • shift, unshift
  • slice, splice
  • forEach, map, filter, reduce

Задача: знайти найбільше число у масиві

function findMax(arr) {
  return Math.max(...arr);
}

2️⃣ Об’єкти (Object) та Map

Об’єкти — це колекції пар “ключ-значення”, де ключі — рядки, а значенням може буть чим в JS.

Приклад об’єкту:

const user = { name: "John", age: 30 };
console.log(user["age"]); // 30

Map дозволяє використовувати будь-який тип ключа:

const map = new Map();
map.set("name", "Alice");
map.set(123, true);
console.log(map.get("name")); // Alice

3️⃣ Set (множина)

Set — колекція унікальних значень.

Приклад:

const numbers = new Set([1, 2, 2, 3]);
console.log(numbers); // Set {1, 2, 3}

4️⃣ Стек (Stack)

Принцип: LIFO — “останній прийшов — перший вийшов” – як млинці, що бабусі приготувала – останній млинчик зверху, але ти його з’їдаєш першим!

Приклад:

const stack = [];
stack.push(1);
stack.push(2);
console.log(stack.pop()); // 2

Застосування:

  • історія сторінок у браузері
  • зворотний обхід дерева
  • парсер коду (перевірка дужок)
  • стек в EventLoop

5️⃣ Черга (Queue)

Принцип: FIFO — “перший прийшов — перший вийшов” – це як черга в крамниці: хто перший зайняв чергу, того першого і обслужили і він пішов по своїх справах, потім – наступний покупець, а так далі, поки черга не скінчиться.

Реалізація:

const queue = [];
queue.push(1);
queue.push(2);
console.log(queue.shift()); // 1

Застосування:

  • асинхронні події
  • обробка запитів у черзі

6️⃣ Зв’язаний список (Linked List)

У JavaScript немає вбудованого типу, але його можна реалізувати:

class Node {
  constructor(value) {
    this.value = value;
    this.next = null;
  }
}

const node1 = new Node(10);
const node2 = new Node(20);
node1.next = node2;

Перевага: швидке додавання/видалення елементів
Недолік: повільний доступ до елементів

Черги використовують часто, коли треба щось швидко додати або видалити, і коли не дуже часто потрібен доступ до елементів.


7️⃣ Графи та дерева (основи)

  • Дерева: ієрархічна структура (DOM, файлові системи)
  • Графи: вузли з довільними зв’язками (соцмережі, маршрути)
const tree = {
  value: 1,
  children: [
    { value: 2 },
    { value: 3 }
  ]
};

🔄 Часті шаблони алгоритмів (підходи)

Назва шаблонуСуть
Two PointersДва індекси, що сходяться/розходяться
Sliding WindowПідмасиви фіксованого/динамічного розміру
Divide and ConquerРозділити задачу на частини (наприклад, Merge Sort)
RecursionВиклик функції зменшеної версії себе
Hashing / FrequencyЗберігання частот у Map/Object
BacktrackingПошук з поверненням (лабіринт, Sudoku, комбінації)

🧪 Приклад практичної задачі: Підрахунок входжень символів

function countChars(str) {
  const map = {};
  for (let char of str) {
    map[char] = (map[char] || 0) + 1;
  }
  return map;
}
console.log(countChars("hello")); // {h: 1, e: 1, l: 2, o: 1}

🔧 Інструменти для практики

  • 💻 Playcode.io — онлайн-редактор для JS
  • 💪 LeetCode, Codewars — задачі на алгоритми
  • 📘 Grokking Algorithms (наочна книга)
  • 📺 YouTube канали: Techsith, WilliamFiset, freeCodeCamp

🧩 Висновок

Знання алгоритмів і структур даних у JavaScript — це ключ до створення швидкого, надійного та масштабованого коду. Розуміння Big O, ефективне використання масивів, об’єктів, стеку, черг і інших структур допомагає вирішувати складні задачі й готуватись до співбесід.

Навички алгоритмічного мислення — це не просто теорія, а практичний інструмент кожного розробника.


Якщо ти веброзробник або готуєшся до технічного інтерв’ю — ця база зробить тебе на голову вищим серед конкурентів.

Нижче Ви знайдете детальне пояснення ролі методів .map(), .filter() і .reduce() в алгоритмах на JavaScript — з практичними прикладами та фокусом на ефективність, продуктивність і чистоту коду.


🔍 Методи масивів .map(), .filter(), .reduce() і їх роль в алгоритмах

📌 Що спільного?

Ці методи — не мутують оригінальний масив і забезпечують декларативний, чистий, функціональний стиль коду.
Їх часто використовують у сучасних алгоритмах обробки даних, наприклад:

  • трансформація даних (.map)
  • фільтрація (.filter)
  • акумуляція або агрегація (.reduce)

🔹 .map() — трансформація даних

Метод створює новий масив, у якому кожен елемент обробляється функцією.

const numbers = [1, 2, 3];
const doubled = numbers.map(n => n * 2);
console.log(doubled); // [2, 4, 6]

🔧 Алгоритмічна роль:

  • Преобразування масиву без зміни довжини
  • Часто використовується в препроцесингу даних (наприклад, API → UI)

🧠 Big O:

  • Часова складність: O(n)
  • Пам’ять: O(n) (створює новий масив)

🔹 .filter() — фільтрація елементів

Повертає новий масив, який містить тільки ті елементи, що задовольняють умову.

const numbers = [1, 2, 3, 4, 5];
const even = numbers.filter(n => n % 2 === 0);
console.log(even); // [2, 4]

🔧 Алгоритмічна роль:

  • Використовується у фільтрації даних, наприклад:
    • Пошук елементів
    • Валідація
    • Вилучення непотрібного

🧠 Big O:

  • Часова складність: O(n)
  • Пам’ять: O(k), де k — кількість відібраних елементів

🔹 .reduce() — агрегація / акумуляція

Повертає одне значення: число, обʼєкт, масив тощо.
Застосовується для зведення всього масиву до одного результату.

const numbers = [1, 2, 3, 4];
const sum = numbers.reduce((acc, curr) => acc + curr, 0);
console.log(sum); // 10

🔧 Алгоритмічна роль:

  • Використовується в:
    • Обчисленні сум, добутків, середніх
    • Створенні словників/мап
    • Групуванні даних (groupBy)

🧩 Приклад: частотний аналіз символів

const str = "hello";
const freq = str.split('').reduce((acc, char) => {
  acc[char] = (acc[char] || 0) + 1;
  return acc;
}, {});
console.log(freq); // {h:1, e:1, l:2, o:1}

🧠 Big O:

  • Часова складність: O(n)
  • Пам’ять: залежить від типу результату

🤝 Комбінація методів

Комбінування .filter() + .map() + .reduce() — це потужна техніка в алгоритмах:

Приклад: сума квадратів парних чисел

const arr = [1, 2, 3, 4, 5];
const result = arr
  .filter(n => n % 2 === 0)
  .map(n => n ** 2)
  .reduce((acc, n) => acc + n, 0);

console.log(result); // 20 (4 + 16)

📈 Коли використовувати:

МетодКоли використовувати
.map()Потрібно перетворити кожен елемент
.filter()Потрібно відфільтрувати частину елементів
.reduce()Потрібно звести все до одного значення/структури

🚀 Переваги використання в алгоритмах:

  • 🧼 Чистий код — читається легко, схоже на “людську мову”
  • 🔁 Ланцюг викликів — легко комбінуються
  • 🔒 Іммутативність — не змінюють оригінальні дані
  • 📊 Візуальна зрозумілість — одразу видно, що саме відбувається

🔥 Альтернатива — цикл for?

Так, іноді цикл for або for...of швидший (менше накладних витрат), особливо для великих масивів або в критично важливому коді.
Але для більшості задач .map(), .filter(), .reduce() = кращий вибір за стиль, безпеку й читабельність.


🧩 Висновок:

Методи .map(), .filter() і .reduce() — це ключові інструменти сучасного JavaScript-розробника. Їх часто застосовують у реальних алгоритмах, при роботі з API, у компонентах React, при вирішенні задач на співбесідах.

Нижче приклади функцій для пошуку мінімального та максимального значення в масиві, а також аналіз їх часової складності (Big O).


🔧 Пошук min у масиві

function findMin(arr) {
  if (arr.length === 0) return undefined;

  let min = arr[0];
  for (let i = 1; i < arr.length; i++) {
    if (arr[i] < min) {
      min = arr[i];
    }
  }
  return min;
}

🧠 Аналіз:

  • Часова складність: O(n) — бо перевіряємо кожен елемент один раз.
  • Просторова складність: O(1) — використовуємо лише одну змінну min.

🔧 Пошук max у масиві

function findMax(arr) {
  if (arr.length === 0) return undefined;

  let max = arr[0];
  for (let i = 1; i < arr.length; i++) {
    if (arr[i] > max) {
      max = arr[i];
    }
  }
  return max;
}

🧠 Аналіз:

  • Часова складність: O(n)
  • Просторова складність: O(1)

🧪 Приклад виклику

const numbers = [5, 3, 9, 1, 7];
console.log(findMin(numbers)); // 1
console.log(findMax(numbers)); // 9

⏱ Альтернатива з Math.min / Math.max

const min = Math.min(...numbers); // O(n)
const max = Math.max(...numbers); // O(n)

❗️ Але ... (spread-оператор) створює копію масиву в пам’яті, тому для дуже великих масивів це може бути неефективно по пам’яті (O(n)).


✅ Висновок:

МетодЧасова складністьПросторова складністьПереваги
for циклO(n)O(1)Контроль, ефективність
Math.min()O(n)O(n) через spreadКоротко, але гірше по пам’яті