$ sudo teach IT
РАЗДЕЛ 11 · УРОК 3

Пузырьковая сортировка

Пузырьковая сортировка — простейший алгоритм, с которого начинается изучение сортировок. Он медленный, но идеально иллюстрирует принцип: постепенно «выталкивать» большие элементы на нужное место. Разберём пошагово с визуализацией и напишем код.

⏱ ~25 минут 🫧 Bubble Sort O(n²)

🫧 Принцип: «пузыри» всплывают вправо

Представьте аквариум с пузырьками разного размера. Большие пузырьки поднимаются быстрее, маленькие — медленнее. Вот именно по этому принципу работает пузырьковая сортировка — большие элементы «всплывают» вправо с каждым проходом.

Алгоритм очень простой. Мы идём по массиву слева направо и сравниваем каждую пару соседних элементов. Если левый больше правого — меняем их местами. Так самый большой элемент за один проход окажется в самом конце. Потом повторяем для оставшейся части.

🔑 Ключевая идея: за каждый полный проход по массиву самый большой из оставшихся элементов занимает своё правильное место в конце. n элементов — нужно не более n проходов.

📊 Пошаговая визуализация: [5, 3, 8, 1, 4]

Разберём пошагово. Проход 1: сравниваем пары соседей и меняем местами, если нужно. После первого прохода максимальный элемент (8) встаёт на своё место.

Шаг Сравниваем Действие Массив после
Начало — — [5, 3, 8, 1, 4]
Проход 1, шаг 1 5 и 3 5 > 3 → меняем [3, 5, 8, 1, 4]
Проход 1, шаг 2 5 и 8 5 < 8 → не меняем [3, 5, 8, 1, 4]
Проход 1, шаг 3 8 и 1 8 > 1 → меняем [3, 5, 1, 8, 4]
Проход 1, шаг 4 8 и 4 8 > 4 → меняем [3, 5, 1, 4, 8]
Итог прохода 1 8 встал на своё место ✅ [3, 5, 1, 4, 8]
Проход 2 Сравниваем первые 4 элемента, 5 «всплывает» вправо [3, 1, 4, 5, 8]
Проход 3 Сравниваем первые 3 элемента [1, 3, 4, 5, 8]
Проход 4 Сравниваем первые 2 элемента [1, 3, 4, 5, 8]
Готово! ✅ Массив отсортирован за 4 прохода [1, 3, 4, 5, 8]

💻 Базовая реализация

Код пузырьковой сортировки — один из самых коротких и понятных в алгоритмике. Два вложенных цикла: внешний считает проходы, внутренний идёт по элементам.

function bubbleSort(arr) {
  const n = arr.length;

  for (let i = 0; i < n; i++) {           // внешний: n проходов
    for (let j = 0; j < n - i - 1; j++) { // внутренний: каждый раз на 1 короче
      if (arr[j] > arr[j + 1]) {
        // меняем местами (деструктуризация ES6)
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
      }
    }
  }

  return arr;
}

console.log(bubbleSort([5, 3, 8, 1, 4]));
// [1, 3, 4, 5, 8]

Почему внутренний цикл идёт до n - i - 1? После каждого прохода i последних элементов уже отсортированы и стоят на своих местах. Нет смысла проверять их снова.

🔄 Замена через деструктуризацию: [a, b] = [b, a] — современный ES6-способ поменять два значения местами без временной переменной. Раньше писали: let tmp = a; a = b; b = tmp;

🚀 Оптимизация: флаг для раннего выхода

Базовая реализация всегда делает n² сравнений — даже если массив уже отсортирован! Это расточительно. Добавим простую оптимизацию: если за целый проход не было ни одной замены — массив уже отсортирован, выходим досрочно.

function bubbleSortOptimized(arr) {
  const n = arr.length;

  for (let i = 0; i < n; i++) {
    let swapped = false; // флаг: были ли замены в этом проходе?

    for (let j = 0; j < n - i - 1; j++) {
      if (arr[j] > arr[j + 1]) {
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
        swapped = true; // была замена — отмечаем
      }
    }

    if (!swapped) {
      // Если ни одной замены — массив уже отсортирован!
      break;
    }
  }

  return arr;
}

// Для уже отсортированного массива — всего один проход!
console.log(bubbleSortOptimized([1, 2, 3, 4, 5]));
// [1, 2, 3, 4, 5] — быстро! O(n) в лучшем случае

Эта оптимизация меняет лучший случай с O(n²) на O(n). Если передать уже отсортированный массив — алгоритм сделает всего один проход и выйдет.

Лучший случай

Массив уже отсортирован → один проход → O(n)

Средний случай

Случайный порядок → около n/2 проходов → O(n²)

Худший случай

Массив отсортирован в обратном порядке → n проходов → O(n²)

🔢 Почему сложность именно O(n²)

Посчитаем количество сравнений для массива из n элементов:

  • Проход 1: n−1 сравнений
  • Проход 2: n−2 сравнений
  • Проход 3: n−3 сравнений
  • ... и так до последнего прохода: 1 сравнение

Итого: (n−1) + (n−2) + ... + 1 = n(n−1)/2. При больших n это примерно n²/2. В Big O константы отбрасываем — получаем O(n²).

// Для n = 5 элементов:
// Проход 0: 4 сравнения (j от 0 до 3)
// Проход 1: 3 сравнения (j от 0 до 2)
// Проход 2: 2 сравнения (j от 0 до 1)
// Проход 3: 1 сравнение  (j = 0)
// Итого: 4 + 3 + 2 + 1 = 10 сравнений = 5*4/2

// Для n = 1000:
// 1000 * 999 / 2 = 499 500 сравнений

// Для n = 1 000 000:
// ~500 000 000 000 сравнений = миллиард!

⚠️ Вывод: пузырьковая сортировка хороша для обучения и понимания принципов, но в реальном коде никогда не используйте её для больших массивов. При 10 000+ элементов — только встроенный .sort() или быстрая/слиянием.

✅ Когда пузырьковая сортировка полезна

Несмотря на все ограничения, есть ситуации, где пузырьковая сортировка — разумный выбор:

📚
Учебные цели

Идеально для объяснения концепции сортировки — простой, понятный, легко визуализировать

📦
Очень маленькие массивы

До 10–20 элементов разница между алгоритмами незначительна. Простота кода важнее

🔍
Почти отсортированный массив

С флагом early-exit алгоритм работает за O(n) — лучше многих конкурентов в этом частном случае

✅ Итоги урока

  • Пузырьковая сортировка сравнивает соседние элементы и переставляет их — большие «всплывают» вправо
  • За каждый полный проход самый большой из оставшихся элементов встаёт на своё место
  • Два вложенных цикла → сложность O(n²) в среднем и худшем случае
  • Флаг swapped позволяет выйти досрочно — лучший случай становится O(n)
  • Для массива [5, 3, 8, 1, 4] нужно 4 прохода и 10 сравнений
  • На практике не используется для больших данных — только учебные цели и маленькие массивы

В следующем уроке — быстрая сортировка: умный алгоритм «разделяй и властвуй», который работает в O(n log n). ⚡

Пузырьковая сортировка

6 вопросов

Реализация пузырьковой сортировки

Реализуйте функцию bubbleSort(arr), которая сортирует массив чисел по возрастанию методом пузырьковой сортировки. Функция должна мутировать исходный массив (сортировать на месте) и возвращать его. Используйте два вложенных цикла. Внутренний цикл должен идти до n - i - 1.

Оптимизированная пузырьковая сортировка

Реализуйте функцию optimizedBubbleSort(arr), которая сортирует массив чисел по возрастанию, но с оптимизацией: если за полный проход не было ни одной замены — алгоритм завершается досрочно (флаг swapped). Функция должна возвращать отсортированный массив.