Содержание
  1. Принцип работы
  2. Разбиение по шагам на примере
  3. Особенности и преимущества
  4. Схемы разбиения: Ломуто и Хоара
  5. Реализация на Python
  6. Худший случай и как его избежать
  7. Сравнение с другими методами
  8. Частые вопросы
  9. Заключение
СправочникОбновлено · 06.2026

Как работает быстрая сортировка

26 апреля 2024 · 7 минут чтения

Быстрая сортировка в Python

Быстрая сортировка (quicksort) — алгоритм класса «разделяй и властвуй»: выбирается опорный элемент, массив делится на части «меньше опорного» и «больше опорного», после чего те же шаги рекурсивно применяются к каждой части. Средняя сложность — O(n log n). Алгоритм придумал британский учёный Тони Хоар в 1960 году. Ниже разберём принцип, разбиение по шагам, реализацию на Python и сравнение с другими сортировками.

Быстрая сортировка quicksort

Принцип работы

Три шага быстрой сортировки: выбор опорного, разбиение, рекурсия
Три шага быстрой сортировки: выбор опорного, разбиение, рекурсия

Быстрая сортировка использует стратегию «разделяй и властвуй». Алгоритм состоит из трёх основных шагов:

  1. Выбор опорного элемента. Из массива выбирается один элемент, который будет служить опорным (pivot). Критерии выбора могут различаться: первый элемент, последний, средний или выбранный случайным образом.
  2. Разбиение (partition). Массив перестраивается так, что элементы меньше опорного помещаются перед ним, а большие или равные — после. После этого опорный элемент стоит на своей конечной позиции в готовом массиве.
  3. Рекурсия. Те же два шага применяются к двум подмассивам, образованным слева и справа от опорного. Рекурсию не применяют к массиву из одного элемента — он уже отсортирован.

Алгоритм разработал Тони Хоар в 1960 году во время работы в МГУ — изначально для задачи машинного перевода. С тех пор quicksort остаётся одним из самых используемых методов сортировки в стандартных библиотеках языков программирования.

Разбиение массива относительно опорного элемента

Разбиение по шагам на примере

Разбиение массива [3, 6, 8, 10, 1, 2, 1], опорный — последний элемент
Разбиение массива [3, 6, 8, 10, 1, 2, 1], опорный — последний элемент

Разберём один проход на массиве [3, 6, 8, 10, 1, 2, 1]. В качестве опорного возьмём последний элемент. Сначала массив делится относительно опорного, затем те же шаги повторяются для левой и правой частей, пока в подмассиве не останется один элемент.

  1. Опорный элемент — 1 (последний). Все остальные элементы сравниваются с ним.
  2. Элементы меньше или равные опорному уходят влево: [1]. Большие — вправо: [3, 6, 8, 10, 2].
  3. Левая часть [1] состоит из одного элемента — она уже отсортирована.
  4. Правая часть [3, 6, 8, 10, 2] обрабатывается рекурсивно: выбирается новый опорный и снова идёт разбиение.
  5. Результаты собираются в порядке «левое + опорный + правое» — получается отсортированный массив [1, 1, 2, 3, 6, 8, 10].

Особенности и преимущества

  • Эффективность на больших данных. Средняя временная сложность — O(n log n), что делает quicksort одной из самых быстрых сортировок для больших объёмов данных.
  • Сортировка на месте. Оптимизированная реализация требует лишь O(log n) дополнительной памяти под стек рекурсии, не выделяя новые массивы.
  • Адаптивность. Производительность сильно зависит от выбора опорного элемента — при удачном выборе алгоритм работает близко к лучшему случаю.
  • Нестабильность. Quicksort не сохраняет относительный порядок равных элементов — это плата за скорость.

Схемы разбиения: Ломуто и Хоара

Разбиение можно реализовать по-разному. Две классические схемы:

  • Схема Ломуто. Использует один указатель i. Опорным обычно берётся последний элемент. Алгоритм проходит массив и каждый элемент, не больший опорного, переносит в левую часть. Проще для понимания, но делает больше обменов.
  • Схема Хоара. Использует два указателя — с начала и с конца массива, которые движутся навстречу друг другу и меняют местами пары элементов, стоящих «не на своей стороне». Эффективнее по числу обменов, но сложнее в реализации.

Реализация на Python

Начнём с самой наглядной версии. Этот пример использует рекурсию и list comprehension: опорным берётся последний элемент, а вспомогательные списки собирают элементы меньше и больше опорного.

def quick_sort(arr):
    if len(arr) <= 1:
        return arr
    pivot = arr.pop()                 # последний элемент как опорный
    lesser_than_pivot = []            # элементы меньше или равные опорному
    greater_than_pivot = []           # элементы больше опорного
    for element in arr:
        if element > pivot:
            greater_than_pivot.append(element)
        else:
            lesser_than_pivot.append(element)
    # рекурсивно сортируем подмассивы и склеиваем результат
    return quick_sort(lesser_than_pivot) + [pivot] + quick_sort(greater_than_pivot)

# Пример использования
arr = [3, 6, 8, 10, 1, 2, 1]
print("Готовый массив:", quick_sort(arr))

Этот код прост для чтения, но создаёт дополнительные списки на каждом уровне рекурсии. Для экономии памяти применяют разбиение «на месте» по схеме Ломуто — массив сортируется внутри себя, без вспомогательных списков:

def quick_sort(arr, low=0, high=None):
    if high is None:
        high = len(arr) - 1
    if low < high:
        p = partition(arr, low, high)     # опорный встаёт на своё место
        quick_sort(arr, low, p - 1)       # сортируем левую часть
        quick_sort(arr, p + 1, high)      # сортируем правую часть
    return arr

def partition(arr, low, high):
    pivot = arr[high]                     # опорный — последний элемент
    i = low - 1
    for j in range(low, high):
        if arr[j] <= pivot:
            i += 1
            arr[i], arr[j] = arr[j], arr[i]
    arr[i + 1], arr[high] = arr[high], arr[i + 1]
    return i + 1

print(quick_sort([3, 6, 8, 10, 1, 2, 1]))

Обе функции дают один результат — отсортированный массив [1, 1, 2, 3, 6, 8, 10]. Первая удобнее для обучения, вторая ближе к тому, как quicksort реализуют на практике.

Как узнать быструю сортировку в чужом коде

Если перед вами какая-то реализация алгоритма быстрой сортировки, её выдают характерные признаки:

  • есть выбор опорного элемента (pivot) — первого, последнего, среднего или случайного;
  • массив делится на части относительно опорного (функция partition или сборка списков «меньше/больше»);
  • функция вызывает сама себя для левой и правой части — это рекурсия;
  • результат получается склейкой подмассивов или сортировкой на месте через обмены элементов.

Худший случай и как его избежать

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

  • Случайный опорный элемент — рандомизация ломает «плохие» входные данные.
  • Медиана трёх — опорным берётся медиана из первого, среднего и последнего элементов.
  • Introsort — гибрид, который при слишком глубокой рекурсии переключается на пирамидальную сортировку (heap sort) и гарантирует O(n log n).

Сравнение с другими методами

По сравнению с сортировкой слиянием quicksort на практике часто быстрее, хотя у обоих средняя сложность O(n log n). В отличие от слияния, быстрая сортировка не требует значительной дополнительной памяти, что делает её предпочтительной в системах с ограниченным объёмом памяти. Сравним основные алгоритмы по сложности, памяти и стабильности.

Алгоритм сортировки Лучший случай Средний случай Худший случай Дополнительная память Стабильность
Быстрая сортировка O(n log n) O(n log n) O(n²) O(log n) Нестабильная
Сортировка слиянием O(n log n) O(n log n) O(n log n) O(n) Стабильная
Пузырьковая сортировка O(n) O(n²) O(n²) O(1) Стабильная
Сортировка вставками O(n) O(n²) O(n²) O(1) Стабильная
Сортировка выбором O(n²) O(n²) O(n²) O(1) Нестабильная

Объяснение терминов

  • Временная сложность — мера количества операций для сортировки массива. Показывает, как растёт время выполнения с увеличением размера входных данных.
  • Дополнительная память — объём памяти, который требуется алгоритму помимо исходного массива.
  • Стабильность — алгоритм стабилен, если сохраняет относительный порядок одинаковых элементов.

Выводы

  • Быстрая сортировка и сортировка слиянием дают хорошую производительность на больших данных, но слияние требует значительно больше дополнительной памяти.
  • Пузырьковая и вставками подходят для небольших или почти отсортированных массивов, но неэффективны на больших данных.
  • Сортировка выбором имеет квадратичную сложность независимо от входных данных и в целом медленнее остальных рассмотренных методов.

Частые вопросы

Кто придумал быструю сортировку?

Алгоритм разработал британский учёный Тони Хоар в 1960 году во время работы в МГУ. Поэтому быструю сортировку также называют сортировкой Хоара.

Какая сложность у быстрой сортировки?

В среднем и лучшем случае — O(n log n), в худшем — O(n²). Дополнительной памяти в оптимизированной версии требуется O(log n) под стек рекурсии.

Почему быстрая сортировка может работать за O(n²)?

Когда опорный элемент стабильно оказывается минимальным или максимальным (например, на уже отсортированном массиве), разбиение получается неравномерным. Помогают случайный выбор опорного, метод «медианы трёх» или introsort.

Стабильна ли быстрая сортировка?

Нет. В классических реализациях относительный порядок равных элементов не сохраняется.

Чем схема Ломуто отличается от схемы Хоара?

Схема Ломуто использует один указатель и проще в реализации, но делает больше обменов. Схема Хоара использует два встречных указателя и эффективнее по числу обменов.

Заключение

Быстрая сортировка — мощный и универсальный алгоритм, который применяется во множестве задач обработки данных благодаря эффективности и простоте реализации. Способность работать с большими объёмами данных и минимальные требования к дополнительной памяти делают quicksort одним из лучших инструментов в арсенале программиста. Главное — следить за выбором опорного элемента, чтобы не попасть в худший случай. Подробнее об истории и схемах разбиения можно прочитать в Википедии.

Читайте также

3 материала