Вопрос с собеседования · Основы

Можно ли отсортировать быстрее, чем за O(n log n)?

MiddleСортировки
Ещё спрашивают так:
  • Почему сортировка сравнением не может быть быстрее n log n?
  • Что такое сортировка подсчётом и поразрядная сортировка?
Коротко

Сравнениями — нельзя: любой алгоритм, который только сравнивает элементы, в худшем случае делает Ω(n log n) сравнений. Без сравнений — можно при ограничениях на ключи.

  • Нижняя граница: n! перестановок требуют дерево решений глубины log₂(n!) ≈ n log n
  • Сортировка подсчётом — O(n + k) для целых в небольшом диапазоне k

А ещё Поразрядная (radix) — O(d·(n + k)) по цифрам; блочная (bucket) — для равномерно распределённых данных

Подробно

Почему n log n — предел для сравнений. Алгоритм сортировки сравнением можно представить как дерево решений: в каждом узле — сравнение двух элементов, в каждом листе — конкретная перестановка, которая сортирует вход. Различных входов-перестановок n!, и каждая должна вести в свой лист. Двоичное дерево с n! листьями имеет высоту не меньше log₂(n!), а это порядка n log n (по формуле Стирлинга). Высота — число сравнений в худшем случае. Значит, слияние и пирамидальная сортировка асимптотически оптимальны.

Как обойти ограничение — не сравнивать, а использовать структуру ключей:

  • сортировка подсчётом — для целых чисел в диапазоне 0..k: считаем, сколько раз встречается каждое значение, затем выписываем по порядку. O(n + k) времени и O(k) памяти. Выгодна, когда k не больше порядка n (оценки, возраст, байты);
  • поразрядная (radix) — сортируем устойчиво по каждой цифре, от младшей к старшей, сортировкой подсчётом. O(d·(n + k)), где d — число разрядов. Подходит для целых, строк фиксированной длины, IP-адресов;
  • блочная (bucket) — раскладываем по корзинам по значению и сортируем корзины; линейная в среднем для равномерно распределённых чисел.

Ограничения: нужна информация о ключах (целые, диапазон, длина), много памяти при большом k; для произвольных объектов с компаратором остаётся O(n log n).

Пример

def counting_sort(a, max_value):
    counts = [0] * (max_value + 1)
    for x in a:
        counts[x] += 1
    return [v for v, c in enumerate(counts) for _ in range(c)]

counting_sort([3, 1, 4, 1, 5, 2], 5)   # [1, 1, 2, 3, 4, 5]

Что хотят услышать

  • Ответ: сравнениями нельзя, без сравнений — можно при ограничениях.
  • Идея доказательства через дерево решений и n!.
  • Сортировка подсчётом и поразрядная, их сложность и условия применимости.

Частые ошибки

  • Утверждать, что сортировка подсчётом «всегда лучше» — при большом диапазоне она расточительна.
  • Не знать, откуда берётся оценка n log n.

Проверьте себя

Один верный вариант. Подумайте, потом откройте ответ.

Показать варианты и ответ
  • ✗Любую сортировку можно сделать за O(n)
  • ✗Нельзя никак: нижняя граница n log n доказана для всех алгоритмов сортировки, включая сортировку подсчётом и поразрядную, при любых ключах
  • ✓Сортировка сравнениями не быстрее Ω(n log n) в худшем случае; без сравнений, при ограничениях на ключи (подсчёт, radix), — быстрее
  • ✗Сортировка подсчётом работает для любых ключей

Связанные вопросы

Партнёр платформы

Разбор собеседования с sotto

После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.

Разбор собеседования