Открыть сервисСервис

Быстрая сортировка (алгоритм)

Быстрая сортировка (quick sort) — алгоритм сортировки, основанный на принципе «разделяй и властвуй». Средняя асимптотическая сложность алгоритма составляет O(n log n), при этом в худшем случае она деградирует до O(n²). Быстрая сортировка была разработана в 1959 году британским учёным Чарльзом Хоаром, работавшим тогда в Массачусетском технологическом институте.

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

Алгоритм состоит из трёх шагов:

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

Базис рекурсии — подмассив длиной 0 или 1, который считается отсортированным.

Пример разделения массива [3, 6, 8, 10, 1, 2, 1] с опорным элементом 1 (последний): после прохода массив преобразуется в [1, 1, 8, 10, 3, 6, 2], где все элементы слева от первого 1 не больше него, а справа — больше. Затем рекурсия применяется к левому и правому подмассивам.

Выбор опорного элемента

Стратегия выбора опорного элемента существенно влияет на производительность:

  • Последний или первый элемент массива — простейший вариант, но на уже почти отсортированных данных приводит к худшей сложности O(n²).
  • Случайный элемент — выбор случайного опорного элемента даёт хорошее среднее поведение на любых данных.
  • Медиана трёх (median-of-three) — берётся медиана из первого, среднего и последнего элементов; популярный компромисс между простотой и устойчивостью.
  • Медиана медиан (алгоритм Бладера — Хоара) — выбор опорного элемента за линейное время гарантирует сбалансированное разделение и худшую сложность O(n log n), однако на практике применяется редко из-за больших констант.

Схемы разделения

Существует несколько способов реализации шага partition:

  • Схема Хоара (1961): два указателя движутся навстречу друг другу с обоих концов подмассива; элементы меняются местами, когда левый указатель стоит на элементе больше опорного, а правый — на элементе меньше. Считается более эффективной, чем схема Ломуто, и требует в среднем примерно втрое меньше обменов.
  • Схема Ломуто: один проход слева направо; элементы меньше опорного накапливаются в начале массива. Более простая в реализации, но производит больше обменов.
  • Трёхпутевое разделение (three-way partition, Dijkstra): массив разбивается на три части — «меньше опорного», «равно опорному», «больше опорного». Особенно эффективно при наличии большого числа повторяющихся ключей.

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

Худший случай (O(n²)) возникает, когда разделение каждый раз даёт крайне несбалансированные подмассивы — например, при выборе наименьшего или наибольшего элемента в качестве опорного на уже отсортированном массиве. Лучший случай (O(n log n)) достигается при идеально сбалансированных разделениях.

Для защиты от худшего случая на практике применяют:

  • случайный выбор опорного элемента;
  • гибридную стратегию: при малом размере подмассива (обычно 8–16 элементов) переходить к сортировке вставками;
  • гибридную стратегию: при малом размере подмассива (обычно 8–16 элементов) переходить к сортировки вставками;
  • introsort — алгоритм, начинающийся как быстрая сортировка, но переключающийся на heapsort, если глубина рекурсии превышает пороговое значение (2·log n). Именно introsort (с дополнительным переключением на insertion sort для малых подмассивов) реализован в стандартных библиотеках C++ (std::sort).

Свойства

СвойствоЗначение
Средняя сложностьO(n log n)
Худшая сложностьO(n²) (у классической версии)
Лучшая сложностьO(n log n)
Дополнительная памятьO(log n) стек вызовов (в худшем случае O(n))
УстойчивостьНет
На местеДа (с дополнительной памятью под стек)

Быстрая сортировка неустойчива: равные элементы могут меняться относительным порядком. Устойчивые варианты существуют, но требуют дополнительных затрат.

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

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

Применение

Быстрая сортировка и её вариации (в первую очередь introsort) используются в стандартных библиотеках многих языков программирования. В стандартной библиотеке C++ std::sort реализован на основе introsort. В библиотеке Python list.sort() и sorted() используют тимсорт — гибридную сортировку, основанную на сортировке слиянием и сортировке вставками, однако быстрая сортировка остаётся алгоритмом выбора во многих системных и встраиваемых приложениях, где важна скорость и допустима неустойчивость.

История

Алгоритм быстрой сортировки был разработан Чарльзом Хоаром в 1959—1960 годах во время работы над языком программирования ALGOL 60. Схема разделения, предложенная Хоаром, получила название «схема Хоара». В 1962 году Дональд Кнут в своём труде «The Art of Computer Programming» подробно проанализировал алгоритм и его вариации. В 1975 году Джон Ломуто предложил более простую схему разделения, ставшую популярной в учебных материалах.

Источники

  • Кнут Д. Искусство программирования. Том 3: Сортировка и поиск.
  • Хоар Ч. А. Р. «Algorithm 64: Quicksort», Communications of the ACM, 1961.
  • Sedgewick Р. Algorithms. — Addison-Wesley.
  • Марков М. С., Шишков Ю. Л. Методы сортировки и обработки данных.
Заметили ошибку или не согласны с информацией в статье? Напишите нам support@bfometr.ru