2.2.1 — почему нельзя отсортировать быстрее, чем за n log n2.2.1 — why you can't sort faster than n log n

Сортировки СравнениемComparison-Based Sorts

Сортировка — задача, которую решают тысячи раз в день на любом сервере, и всё же у неё есть доказуемая граница снизу. Здесь: простые O(n²) сортировки как база для понимания, quicksort/merge sort/heapsort как способы достичь Ω(n log n), и почему саму эту границу нельзя обойти, если единственный инструмент — сравнение "меньше/больше".Sorting is a problem solved thousands of times a day on any server, and yet it has a provable lower bound. Here: simple O(n²) sorts as a baseline for understanding, quicksort/merge sort/heapsort as ways to reach Ω(n log n), and why that bound itself can't be beaten if the only tool available is a "less-than/greater-than" comparison.

частый вопрос на собеседованииcommon interview question

Классификация: 15 алгоритмов, каждый — отдельная статьяClassification: 15 algorithms, each its own article

Ниже — минимальная сводка по каждому алгоритму (сложность, память, стабильность, идея в одно предложение); полный разбор с диаграммами, кодом и примерами — на отдельной странице по ссылке в названии.Below is a minimal summary of each algorithm (complexity, memory, stability, one-sentence idea); the full writeup with diagrams, code, and examples is on its own page via the name link.

Первые три — учебная база (одна и та же идея, разные компромиссы). Следующие три — способы гарантированно достичь Ω(n log n) (доказательство ниже), каждый со своим набором уступок по памяти, стабильности или предсказуемости худшего случая. Следующие два — гибриды, которыми реально пользуются стандартные библиотеки: IntroSort лежит в основе std::sort, Timsort — в основе sorted() в Python и Arrays.sort() для объектов в Java. Последние семь карточек (2.2.1.9–2.2.1.15, пока без ссылок) — второстепенные варианты и алгоритмы из других моделей вычислений: Shell/Cocktail/Comb/Gnome — небольшие модификации уже разобранных O(n²)-алгоритмов; Bitonic Sort и Odd-Even Mergesort — сортирующие сети с фиксированной, независимой от данных схемой сравнений (модель, ориентированная на параллельное железо, а не последовательное сравнение); External Merge Sort — тот же merge sort, но в модели, где доминирует стоимость обращения к диску, а не число сравнений в памяти.The first three are the textbook baseline (the same idea, different trade-offs). The next three are ways to guarantee Ω(n log n) (proof below), each with its own concession on memory, stability, or worst-case predictability. The next two are hybrids actually used by standard libraries: IntroSort underlies std::sort, Timsort underlies Python's sorted() and Java's Arrays.sort() for objects. The last seven cards (2.2.1.9–2.2.1.15, not yet linked) are minor variants and algorithms from other computational models: Shell/Cocktail/Comb/Gnome are small tweaks on the O(n²) algorithms already covered; Bitonic Sort and Odd-Even Mergesort are sorting networks with a fixed, data-independent comparison schedule (a model aimed at parallel hardware rather than sequential comparison); External Merge Sort is the same merge sort, but in a model where disk-access cost dominates rather than the number of in-memory comparisons.

Нижняя граница Ω(n log n) — и почему её нельзя обойтиThe Ω(n log n) lower bound — and why it can't be beaten

Утверждение "любая сортировка сравнением требует Ω(n log n) сравнений в худшем случае" — не эмпирическое наблюдение, а доказанная теорема, и доказательство удивительно простое. Любой алгоритм сортировки сравнением можно представить как дерево решений (decision tree): каждый внутренний узел — это одно сравнение "a[i] < a[j]?", каждый лист — одна из n! возможных перестановок входа (тот порядок, в который алгоритм в итоге расставит элементы). Чтобы дерево умело различить все n! перестановок, у него должно быть как минимум n! листьев.The claim "any comparison-based sort requires Ω(n log n) comparisons in the worst case" isn't an empirical observation but a proven theorem, and the proof is surprisingly simple. Any comparison-based sorting algorithm can be modeled as a decision tree: every internal node is one comparison "a[i] < a[j]?", every leaf is one of the n! possible input permutations (the order the algorithm ultimately arranges the elements into). For the tree to be able to distinguish all n! permutations, it must have at least n! leaves.

a<b? b<c? a<c? a,b,c a,c,b c,a,b b,a,c n=3: n!=6 листьев (два узла здесь опущены для краткости) → высота ≥ log₂6 ≈ 2.6 → минимум 3 сравнения в худшем случае

каждое сравнение — это один бит различения; чтобы различить n! исходов, нужно log₂(n!) битeach comparison is one bit of information; distinguishing n! outcomes requires log₂(n!) bits

Бинарное дерево высоты h имеет не более 2^h листьев, значит для n! листьев нужна высота h ≥ log₂(n!). По формуле Стирлинга log₂(n!) = Θ(n log n) — то есть любое дерево решений, различающее n! перестановок, имеет высоту Ω(n log n), а высота дерева — это в точности число сравнений в худшем случае вдоль самого длинного пути от корня до листа. Отсюда: ни один алгоритм, использующий только сравнения "меньше/больше", не может гарантированно сортировать быстрее, чем за Ω(n log n) в худшем случае — это не ограничение конкретных алгоритмов (quicksort, merge sort), а фундаментальный предел самой модели вычисления.A binary tree of height h has at most 2^h leaves, so n! leaves require height h ≥ log₂(n!). By Stirling's formula, log₂(n!) = Θ(n log n) — meaning any decision tree distinguishing n! permutations has height Ω(n log n), and a tree's height is exactly the number of comparisons in the worst case along the longest root-to-leaf path. Hence: no algorithm using only "less-than/greater-than" comparisons can guarantee sorting faster than Ω(n log n) in the worst case — this isn't a limitation of specific algorithms (quicksort, merge sort) but a fundamental limit of the computational model itself.

Это же объясняет, почему следующая тема (2.2.2) вообще существует: единственный способ обойти Ω(n log n) — выйти за пределы модели "только сравнения" и использовать структуру самих значений (например, что они целые числа в известном диапазоне). Counting sort, radix sort и bucket sort делают именно это — они не сортировки сравнением, поэтому теорема о нижней границе на них не распространяется.This also explains why the next topic (2.2.2) exists at all: the only way around Ω(n log n) is to step outside the "comparisons only" model and exploit the structure of the values themselves (e.g. that they're integers in a known range). Counting sort, radix sort, and bucket sort do exactly that — they aren't comparison-based sorts, so the lower-bound theorem doesn't apply to them.

5 примеров из реальной жизни5 real-world examples

01

std::sort в libstdc++ — IntroSort на практикеin libstdc++ — IntroSort in practice

std::sort не гарантирует стабильность именно потому, что использует IntroSort (quicksort + heapsort + insertion sort) — если стабильность нужна, стандарт предлагает отдельную функцию std::stable_sort (merge sort), с осознанным компромиссом по памяти.std::sort doesn't guarantee stability precisely because it uses IntroSort (quicksort + heapsort + insertion sort) — if stability is needed, the standard offers a separate std::stable_sort function (merge sort), with a deliberate memory trade-off.

02

Python's Timsort на почти отсортированных логахon nearly-sorted logs

Логи, отсортированные по времени и слитые из нескольких источников (почти отсортированные "прогоны" вперемешку) — ровно тот случай, для которого проектировался Timsort: на реальных данных он часто ближе к O(n), чем к O(n log n), потому что напрямую использует уже упорядоченные подпоследовательности вместо разбиения вслепую.Logs sorted by timestamp and merged from several sources (nearly-sorted "runs" interleaved) — exactly the case Timsort was designed for: on real-world data it's often closer to O(n) than O(n log n), because it directly exploits already-ordered subsequences instead of splitting blindly.

03

Внешняя сортировка терабайтных файлов — merge sort вне памятиExternal sorting of terabyte-scale files — merge sort outside RAM

Когда данные не помещаются в оперативную память, стандартный подход — отсортировать куски, помещающиеся в память, записать их на диск, затем слить (merge) k отсортированных файлов за один линейный проход — та же идея слияния, что у merge sort, но на уровне файлов, а не массивов.When data doesn't fit in RAM, the standard approach is to sort chunks that do fit, write them to disk, then merge k sorted files in a single linear pass — the same merging idea as merge sort, just at the level of files rather than arrays.

04

Атака на наивный quicksort через adversarial входAttacking naive quicksort with an adversarial input

Если сервер сортирует пользовательский вход quicksort'ом с фиксированным (например, всегда первым элементом) выбором pivot, злоумышленник может сконструировать вход, вызывающий Θ(n²) — известная DoS-уязвимость нескольких старых веб-фреймворков; решение — случайный выбор pivot или median-of-three, устраняющие возможность детерминированно предсказать худший случай.If a server sorts user input with quicksort using a fixed pivot choice (e.g. always the first element), an attacker can construct an input triggering Θ(n²) — a known DoS vulnerability in several older web frameworks; the fix is randomized pivot selection or median-of-three, which removes the ability to deterministically predict the worst case.

05

Heapsort во встраиваемых системах с жёстким лимитом памятиHeapsort in embedded systems with a hard memory limit

Гарантированный in-place Θ(n log n) без риска Θ(n²) (в отличие от quicksort) и без O(n) дополнительной памяти (в отличие от merge sort) делает heapsort разумным выбором там, где память жёстко ограничена, а предсказуемость важнее сырой средней скорости — например, в firmware с фиксированным бюджетом RAM.A guaranteed in-place Θ(n log n) with no risk of Θ(n²) (unlike quicksort) and no O(n) extra memory (unlike merge sort) makes heapsort a sensible choice where memory is tightly constrained and predictability matters more than raw average speed — e.g. in firmware with a fixed RAM budget.

ГраблиGotchas

"Quicksort — O(n log n)" без оговорки про худший случайSaying "quicksort is O(n log n)" without the worst-case caveat

Строго говоря, quicksort — это Θ(n log n) в среднем и Θ(n²) в худшем случае; на собеседовании стоит явно проговорить это различие, особенно если следующий вопрос — "а что если массив уже отсортирован" (наивный выбор pivot = первый элемент даёт худший случай именно на отсортированном входе).Strictly speaking, quicksort is Θ(n log n) on average and Θ(n²) worst case; in an interview it's worth stating this distinction explicitly, especially if the follow-up question is "what if the array is already sorted" (a naive pivot = first-element choice triggers the worst case exactly on sorted input).

Путать "in-place" с "не использует дополнительную память вообще"Confusing "in-place" with "uses no extra memory at all"

Quicksort и heapsort называют in-place, но quicksort всё равно тратит O(log n) памяти на стек рекурсии (или O(n) в худшем случае без хвостовой оптимизации) — "in-place" означает "не требует O(n) дополнительной памяти для копии данных", а не "нулевые дополнительные затраты".Quicksort and heapsort are called in-place, but quicksort still spends O(log n) memory on the recursion stack (or O(n) worst case without tail-call optimization) — "in-place" means "doesn't require O(n) extra memory for a data copy," not "zero additional cost."

Забыть, что heapsort и quicksort нестабильныForgetting that heapsort and quicksort are unstable

Если сортируются записи с несколькими ключами (например, сначала по имени, затем повторная сортировка по отделу, ожидая, что порядок имён внутри отдела сохранится) — quicksort/heapsort могут перемешать записи с равным ключом, тихо ломая эту логику; для такого сценария нужна именно стабильная сортировка (merge sort, Timsort).If records with multiple keys are sorted (e.g. first by name, then re-sorted by department, expecting the name order within a department to be preserved) — quicksort/heapsort can shuffle records with equal keys, silently breaking that logic; this scenario specifically needs a stable sort (merge sort, Timsort).