article-spots
article-carousel-spots
programs
Технологии

Разбираем быструю сортировку

27 сент. 2021

Сегодня расскажем об одном из самых эффективных и широко применяемых на практике алгоритмов сортировки — быстрой сортировке, или Quick Sort, или qsort. Этот алгоритм был разработан более 40 лет назад, отчасти поэтому на практике в чистом виде он не встречается, так как уже есть способы добиться наибольшей эффективности при работе этого алгоритма.

Чтобы понимать, как и зачем этот алгоритм улучшается, нужно разобраться, как он работает в своём первозданном виде. Важно отметить, что для своей вычислительной эффективности этот алгоритм достаточно прост в понимании и реализации.

Алгоритм работает по принципу «разделяй и властвуй» — мы будем разделять массив и применять один и тот же алгоритм к его всё уменьшающимся частям. Общая схема алгоритма выглядит так:

  1. Из массива выбирается элемент, который называется pivot, т. е. опорный элемент.
  2. Далее запускается процедура разделения массива таким образом, чтобы в одной его части находились все элементы, которые меньше или равны опорному элементу, а во второй части — все элементы, которые больше опорного элемента.
  3. Для каждого подмассива, если в них больше двух элементов, рекурсивно запускается процедура, описанная в предыдущем пункте. Если элемента два, то они сравниваются друг с другом и при необходимости меняются местами.


После выполнения этих действий мы получим полностью отсортированный массив.

Рассмотрим подробнее второй пункт, что же там происходит. Для этого возьмём неотсортированный массив элементов [7, 2, 1, 8, 6, 3, 5, 4] и пройдёмся по шагам.

Шаг 1

Для начала выберем опорный элемент — возьмём последний элемент массива, он равен 4. Также введём два счётчика — i и j. Будем считать, что индексация в массиве начинается с 0. Тогда i = -1, а j = 0.

Сравниваем j-й элемент массива [7] с опорным элементом [4]. Если j-й элемент больше опорного, то просто инкрементируем счётчик j. В нашем случае на первом шаге мы так и сделаем.

Теперь j = 1, сравниваем j-й элемент массива [2] с опорным элементом [4]. Если он меньше опорного, то инкрементируем счётчик. Теперь i = 0, и меняем местами i-й и j-й элементы массива. В нашем случае меняем местами нулевой и первый элементы массива, получаем массив [2, 7, 1, 8, 6, 3, 5], опорный элемент [4] и инкрементируем счётчик j. 

Далее шаг за шагом продвигаемся по массиву. 

Шаг 2

Счетчик j = 2, сравниваем [1] и [4]. В этом случае инкрементируем i, i = 1 далее меняем местами i-й и j-й элементы массива [7] и [1], получаем массив [2, 1, 7, 8, 6, 3, 5], опорный элемент [4] и инкрементируем счётчик j.

Шаг 3

Счетчик j = 3, сравниваем [8] и [4]. В этом случае инкрементируем только счётчик j.

Шаг 4

Счётчик j = 4, сравниваем [6] и [4]. В этом случае инкрементируем только счётчик j.

Шаг 5

Счётчик j = 5, сравниваем [3] и [4]. В этом случае инкрементируем i, i = 2 и меняем местами i-й и j-й элементы массива [7] и [3], получаем массив [2, 1, 3, 8, 6, 7, 5], опорный элемент [4] и инкрементируем счётчик j.

Шаг 6

Счётчик j = 6, сравниваем [5] и [4]. Счётчик j можно не инкрементировать, так как мы добрались до конца массива. 

Теперь осталось найти местечко в массиве для нашего опорного элемента. Оно находится на i+1 позиции в массиве. Получаем [2, 1, 3] ≤ [4] < [8, 6, 7, 5]. Но так как подмассивы содержат более двух элементов, необходимо выполнить аналогичную сортировку для подмассива из элементов, которые меньше или равны опорному [2, 1, 3], и элементов, которые больше опорного [8, 6, 7, 5]. 

Далее вышеописанные действия повторяются для каждого из подмассивов до тех пор, пока массив не будет полностью отсортирован.

По этим шагам несложно реализовать алгоритм в коде.

Особенности алгоритма

Теперь поговорим о нюансах. Первый и вполне справедливый вопрос, почему мы выбрали опорный элемент именно так? Ответ прост: потому что так проще. Выбор опорного элемента — задача не из лёгких. То, насколько правильно он выбран, напрямую влияет на вычислительную сложность.

Пример выбора опорного элемента: взять первый, последний и средний элементы массива, отсортировать их и взять тот, что посередине. Таким простым действием можно улучшить качество выбранного опорного элемента.

Преимущества и недостатки алгоритма

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

  • на практике это один из самых быстродействующих алгоритмов внутренней сортировки;
  • алгоритм довольно простой как для понимания, так и для реализации;
  • требует лишь O(n) памяти, в случае улучшенной версии — O(1);
  • допускает естественное распараллеливание для сортировки подмассивов;
  • работает на связных списках;
  • является наиболее эффективным для сортировки большого количества данных.


К недостаткам можно отнести:

  • неустойчивость;
  • вычислительная сложность сильно деградирует при неудачных входных данных.


Этот алгоритм, пусть и с модификациями, используется в различных языках программирования. Например, в Java его реализацию можно встретить в библиотеке java.util.Arrays, в методе sort(). А вот что написано в документации Oracle: “Реализация: в качестве алгоритма используется Dual-Pivot Quick Sort, разработанный Владимиром Ярославским, Джоном Бэнтли и Джошуа Блохом”. Поэтому этот алгоритм определённо достоин вашего внимания.


Ещё больше сортировок вы найдёте здесь: