Быстрая сортировка — различия между версиями
(→Псевдокод) |
(→Разбиение массива) |
||
Строка 21: | Строка 21: | ||
j = r | j = r | ||
while true do | while true do | ||
− | while | + | while A[i] < x do |
i = i + 1 | i = i + 1 | ||
− | while A[j] | + | while A[j] > x do |
j = j - 1 | j = j - 1 | ||
if i < j then поменять A[i] и A[j] | if i < j then поменять A[i] и A[j] |
Версия 18:36, 12 июня 2012
Быстрая сортировка (qsort, сортировка Хоара) — один из самых известных и широко используемых алгоритмов сортировки. Среднее время работы
, что является асимптотически оптимальным временем работы для алгоритма, основанного на сравнении. Хотя время работы алгоритма для массива из элементов в худшем случае может составить , на практике этот алгоритм является одним из самых быстрых.Содержание
Алгоритм
- из массива выбирается некоторый опорный элемент .
- запускается процедура разделения массива, которая перемещает все ключи, меньшие, либо равные , влево от него, а все ключи, большие, либо равные — вправо.
- для обоих подмассивов: если в подмассиве более двух элементов, рекурсивно запускаем для него ту же процедуру..
Псевдокод
<wikitex> Quicksort(A, l, r)
if l < r then q = Partition(A, l, r) Quicksort(A, l, q - 1) Quicksort(A, q + 1, r)</wikitex>
Для сортировки всего массива необходимо выполнить процедуру
.Разбиение массива
Основной шаг алгоритма сортировки — процедура
, которая переставляет элементы массива нужным образом: <wikitex> Partition(A, l, r)x = A[l] i = l j = r while true do while A[i] < x do i = i + 1 while A[j] > x do j = j - 1 if i < j then поменять A[i] и A[j] else return j</wikitex>
Асимптотика
Худшее время работы
Предположим, что мы разбиваем массив так, что одна часть содержит
элементов, а вторая — . Поскольку процедура разбиения занимает время , для времени работы получаем соотношение:
.
Мы видим, что при максимально несбалансированном разбиении время работы составляет
. В частности, это происходит, если массив изначально отсортирован.Среднее время работы
Лемма: |
Время работы алгоритма быстрой сортировки равно . |
Доказательство: |
Пусть Х — полное количество сравнений элементов с опорным за время работы сортировки. Нам необходимо вычислить полное количество сравнений. Переименуем элементы массива как , где наименьший по порядку элемент. Также введем множество .Заметим, что сравнеие каждой пары элементов происходит не больше одного раза, так как элемент сравнивается с опорным, а опорный элемент после разбиения больше не будет участвовать в сравнении. Поскольку каждая пара элементов срановается не более одного раза, полное количество сравнений выражается как , где если произошло сравнение и и , если сравнения не произошло. Применим к обоим частям равенства операцию вычисления матожидания и воспользовавшись ее линейностью получим сравнивается с Осталось вычислить величину сравнивается с — вероятность того, что сравнивается с . Поскольку предполагается, что все элементы в массиве различны, то при выборе в качестве опорного элемента впоследствии не будут сравниваться никакие и для которых . С другой стороны, если выбран в качестве опорного, то он будет сравниваться с каждым элементом кроме себя самого. Таким образом элементы и сравниваются тогда и только тогда когда первым в множестве опорным элементом был выбран один из них.сравнивается с первым опорным элементом был или первым опорным элементом был первым опорным элементом был |
Mатожидание времени работы быстрой сортировки будет
.Улучшения
В случае повторяющихся неудачных разбиений опорным элементом, глубина рекурсии может достичь
, а время работы алгоритма . Существуют различные способы разбиения массива, направленные против худшего случая:- При выборе опорного элемента из данного диапазона случайным образом худший случай становится очень маловероятным и ожидаемое время выполнения алгоритма сортировки — .
- Выбирать опорным элементом средний из трех (первого, среднего и последнего элементов).
- Разбивать массив не на две, а на три части.
Оптимизация глубины рекурсии до O(logn) в худшем случае
Во избежание достижения опасной глубины рекурсии в худшем случае (или при приближении к нему) возможна модификация алгоритма, устраняющая одну ветвь рекурсии: вместо того, чтобы после разделения массива вызывать рекурсивно процедуру разделения для обоих найденных подмассивов, рекурсивный вызов делается только для меньшего подмассива, а больший обрабатывается в цикле в пределах этого же вызова процедуры. С точки зрения эффективности в среднем случае разницы практически нет: накладные расходы на дополнительный рекурсивный вызов и на организацию сравнения длин подмассивов и цикла — примерно одного порядка. Зато глубина рекурсии ни при каких обстоятельствах не превысит
, а в худшем случае вырожденного разделения она вообще будет не более 2 — вся обработка пройдёт в цикле первого уровня рекурсии.Ссылки
Литература
- Т. Кормен, Ч. Лейзерсон, Р. Ривест: Алгоритмы: построение и анализ глава 7