Что такое нотация «О» большое (Big O Notation) в алгоритмах?

«Что такое нотация «О» большое (Big O Notation) в алгоритмах?» — вопрос из категории Алгоритмы и структуры данных, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Нотация "O" большое (Big O Notation) — это математическая нотация, которая описывает асимптотическую верхнюю границу скорости роста времени выполнения или потребления памяти алгоритма в худшем случае при увеличении размера входных данных n.

Ключевая идея: Оценивает эффективность алгоритма, а не его точное время в секундах. Она показывает, как масштабируется алгоритм.

Основные классы сложности (от лучшего к худшему):

Нотация Название Пример Описание роста
O(1) Константная Доступ к элементу массива по индексу Не зависит от n
O(log n) Логарифмическая Бинарный поиск Растет очень медленно
O(n) Линейная Поиск в неотсортированном массиве Пропорционально n
O(n log n) Линейно-логарифмическая Эффективные алгоритмы сортировки (QuickSort, MergeSort) Немного хуже линейного
O(n²) Квадратичная Сортировка пузырьком, два вложенных цикла Быстро ухудшается
O(2ⁿ) Экспоненциальная Решение задачи коммивояжера полным перебором Неприемлемо для больших n

Правила упрощения ("почему" отбрасываем константы и младшие степени): Big O описывает рост при n → ∞. Поэтому:

  1. Отбрасываем константы: O(5n + 10)O(n).
  2. Оставляем самую быстрорастущую функцию: O(n² + n log n + 1000)O(n²).
  3. Игнорируем основание логарифма: O(log₂ n)O(log n).

Практические примеры на Java:

// O(1) - Константное время
int getFirst(int[] arr) {
    return arr[0]; // Одна операция
}

// O(n) - Линейное время
boolean contains(int[] arr, int key) {
    for (int num : arr) { // Цикл по n элементам
        if (num == key) return true;
    }
    return false;
}

// O(n²) - Квадратичное время (пузырьковая сортировка)
void bubbleSort(int[] arr) {
    int n = arr.length;
    for (int i = 0; i < n; i++) { // Внешний цикл: n итераций
        for (int j = 0; j < n - 1; j++) { // Внутренний цикл: ~n итераций
            if (arr[j] > arr[j + 1]) {
                swap(arr, j, j + 1); // Итого: n * n = n² операций
            }
        }
    }
}

// O(log n) - Логарифмическое время (бинарный поиск)
int binarySearch(int[] sortedArr, int key) {
    int left = 0, right = sortedArr.length - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2; // Делим диапазон пополам
        if (sortedArr[mid] == key) return mid;
        if (sortedArr[mid] < key) left = mid + 1;
        else right = mid - 1;
    } // На каждой итерации область поиска уменьшается ВДВОЕ
    return -1;
}

Зачем это нужно? Big O — это язык для обсуждения компромиссов между скоростью и памятью. Выбор алгоритма с лучшей асимптотикой (O(n log n) вместо O(n²)) становится критичным при обработке больших данных.