Быстрая сортировка — различия между версиями
(Worst case when middle element is chosen added) |
м (Worst case changed) |
||
Строка 1: | Строка 1: | ||
− | '''Быстрая сортировка''' ( | + | '''Быстрая сортировка''' (англ. ''quick sort'', сортировка Хоара) {{---}} один из самых известных и широко используемых алгоритмов сортировки. Среднее время работы <Tex>O(n\log{n})</Tex>, что является асимптотически оптимальным временем работы для алгоритма, основанного на сравнении. Хотя время работы алгоритма для массива из <tex>n</tex> элементов в худшем случае может составить <tex>\Theta(n^2)</tex>, на практике этот алгоритм является одним из самых быстрых. |
==Алгоритм== | ==Алгоритм== | ||
− | * из массива выбирается некоторый опорный элемент <tex> | + | * из массива выбирается некоторый опорный элемент <tex>A[i]</tex>. |
− | * запускается процедура разделения массива, которая перемещает все ключи, меньшие, либо равные <tex> | + | * запускается процедура разделения массива, которая перемещает все ключи, меньшие, либо равные <tex>A[i]</tex>, влево от него, а все ключи, большие, либо равные <tex>A[i]</tex> {{---}} вправо. |
* для обоих подмассивов: если в подмассиве более двух элементов, рекурсивно запускаем для него ту же процедуру.. | * для обоих подмассивов: если в подмассиве более двух элементов, рекурсивно запускаем для него ту же процедуру.. | ||
==Псевдокод== | ==Псевдокод== | ||
− | '''function''' quicksort(A, l, r): | + | '''function''' quicksort('''int[]''' A, '''int''' l, '''int''' r): |
'''if''' l < r | '''if''' l < r | ||
q = partition(A, l, r) | q = partition(A, l, r) | ||
Строка 16: | Строка 16: | ||
===Разбиение массива=== | ===Разбиение массива=== | ||
Основной шаг алгоритма сортировки {{---}} процедура <tex>\mathrm{partition}</tex>, которая переставляет элементы массива <tex>A[p..r]</tex> нужным образом: | Основной шаг алгоритма сортировки {{---}} процедура <tex>\mathrm{partition}</tex>, которая переставляет элементы массива <tex>A[p..r]</tex> нужным образом: | ||
− | '''int''' partition(A, l, r): | + | '''int''' partition('''int[]''' A, '''int''' l, '''int''' r): |
x = A[l] | x = A[l] | ||
i = l | i = l | ||
Строка 40: | Строка 40: | ||
===Способ построить массив с максимальным количеством сравнений при выборе среднего элемента в качестве опорного=== | ===Способ построить массив с максимальным количеством сравнений при выборе среднего элемента в качестве опорного=== | ||
− | В некоторых алгоритмах быстрой сортировки в качестве опорного выбирается элемент, который стоит в середине рассматриваемого массива. Рассмотрим массив, на котором быстрая сортировка с выбором среднего элемента в качестве опорного сделает | + | В некоторых алгоритмах быстрой сортировки в качестве опорного выбирается элемент, который стоит в середине рассматриваемого массива. Рассмотрим массив, на котором быстрая сортировка с выбором среднего элемента в качестве опорного сделает <tex>\Theta(n^2)</tex> сравнений. Очевидно, что это будет достигаться при худшем случае (когда при каждом разбиении в одном массиве будет оказываться <tex>1</tex>, а в другом <tex> n - 1 </tex> элемент). |
− | Заполним сначала массив длины <tex>n</tex> элементами от <tex>1</tex> до <tex> n | + | Заполним сначала массив <tex>A</tex> длины <tex>n</tex> элементами от <tex>1</tex> до <tex> n </tex>, затем применим следующий алгоритм (нумерация с нуля): |
− | '''for''' i = | + | '''function''' antiQsort ('''int[]''' A): |
− | + | '''for''' i = 0 '''to''' n - 1 | |
− | + | swap(a[i], a[i/2]) | |
+ | Тогда на каждом шаге в качестве среднего элемента будет ставиться самый крупный элемент. | ||
− | При выполнении <tex>\mathrm{partition}</tex> | + | При выполнении <tex>\mathrm{partition}</tex> делается <tex>\Theta(n)</tex> сравнений из-за того, что с помощью индексов <tex>i</tex> и <tex>j</tex> мы проходим в лучшем случае <tex>\Omega(n)</tex> элементов (если функция прекращает свою работу, как только индексы встречаются), в худшем случае <tex>O(2n)</tex> элементов (если оба индекса полностью проходят массив). При каждом изменении индекса делается сравнение, значит, процедура <tex>\mathrm{partition}</tex> делает <tex>\Theta(n)</tex> сравнений с точностью до константы. |
+ | |||
+ | Разбиение массива будет произведено <tex>\Theta(n)</tex> раз, потому что разбиение производится на массивы длины <tex>1</tex> и <tex> n - 1 </tex> (так как на каждом шаге средний элемент является самым крупным. Количество разбиений доказано в оценке худшего случая). Следовательно, на массиве, который строится описанным выше способом, выполняется <tex>\Theta(n)</tex> <tex>\mathrm{partition}</tex> и <tex>\Theta(n)</tex> сравнений для каждого выполнения <tex>\mathrm{partition}</tex>. Тогда быстрая сортировка выполнит <tex>\Theta(n^2)</tex> сравнений для массива, построенного таким способом. | ||
===Среднее время работы=== | ===Среднее время работы=== |
Версия 16:20, 6 января 2016
Быстрая сортировка (англ. quick sort, сортировка Хоара) — один из самых известных и широко используемых алгоритмов сортировки. Среднее время работы
, что является асимптотически оптимальным временем работы для алгоритма, основанного на сравнении. Хотя время работы алгоритма для массива из элементов в худшем случае может составить , на практике этот алгоритм является одним из самых быстрых.Содержание
Алгоритм
- из массива выбирается некоторый опорный элемент .
- запускается процедура разделения массива, которая перемещает все ключи, меньшие, либо равные , влево от него, а все ключи, большие, либо равные — вправо.
- для обоих подмассивов: если в подмассиве более двух элементов, рекурсивно запускаем для него ту же процедуру..
Псевдокод
function quicksort(int[] A, int l, int r): if l < r q = partition(A, l, r) quicksort(A, l, q - 1) quicksort(A, q + 1, r)
Для сортировки всего массива необходимо выполнить процедуру
.Разбиение массива
Основной шаг алгоритма сортировки — процедура
, которая переставляет элементы массива нужным образом:int partition(int[] A, int l, int r): x = A[l] i = l j = r while true while A[i] < x i = i + 1 while A[j] > x j = j - 1 if i < j swap(A[i++], A[j--]) else return j
Асимптотика
Худшее время работы
Предположим, что мы разбиваем массив так, что одна часть содержит
элементов, а вторая — . Поскольку процедура разбиения занимает время , для времени работы получаем соотношение:
.
Мы видим, что при максимально несбалансированном разбиении время работы составляет
. В частности, это происходит, если массив изначально отсортирован.Способ построить массив с максимальным количеством сравнений при выборе среднего элемента в качестве опорного
В некоторых алгоритмах быстрой сортировки в качестве опорного выбирается элемент, который стоит в середине рассматриваемого массива. Рассмотрим массив, на котором быстрая сортировка с выбором среднего элемента в качестве опорного сделает
сравнений. Очевидно, что это будет достигаться при худшем случае (когда при каждом разбиении в одном массиве будет оказываться , а в другом элемент).Заполним сначала массив
длины элементами от до , затем применим следующий алгоритм (нумерация с нуля):function antiQsort (int[] A): for i = 0 to n - 1 swap(a[i], a[i/2])
Тогда на каждом шаге в качестве среднего элемента будет ставиться самый крупный элемент.
При выполнении
делается сравнений из-за того, что с помощью индексов и мы проходим в лучшем случае элементов (если функция прекращает свою работу, как только индексы встречаются), в худшем случае элементов (если оба индекса полностью проходят массив). При каждом изменении индекса делается сравнение, значит, процедура делает сравнений с точностью до константы.Разбиение массива будет произведено
раз, потому что разбиение производится на массивы длины и (так как на каждом шаге средний элемент является самым крупным. Количество разбиений доказано в оценке худшего случая). Следовательно, на массиве, который строится описанным выше способом, выполняется и сравнений для каждого выполнения . Тогда быстрая сортировка выполнит сравнений для массива, построенного таким способом.Среднее время работы
Лемма: |
Время работы алгоритма быстрой сортировки равно . |
Доказательство: |
Пусть Х — полное количество сравнений элементов с опорным за время работы сортировки. Нам необходимо вычислить полное количество сравнений. Переименуем элементы массива как , где наименьший по порядку элемент. Также введем множество .Заметим, что сравнение каждой пары элементов происходит не больше одного раза, так как элемент сравнивается с опорным, а опорный элемент после разбиения больше не будет участвовать в сравнении. Поскольку каждая пара элементов сравнивается не более одного раза, полное количество сравнений выражается как , где если произошло сравнение и и , если сравнения не произошло. Применим к обоим частям равенства операцию вычисления матожидания и воспользовавшись ее линейностью получим сравнивается с Осталось вычислить величину сравнивается с — вероятность того, что сравнивается с . Поскольку предполагается, что все элементы в массиве различны, то при выборе в качестве опорного элемента впоследствии не будут сравниваться никакие и для которых . С другой стороны, если выбран в качестве опорного, то он будет сравниваться с каждым элементом кроме себя самого. Таким образом элементы и сравниваются тогда и только тогда когда первым в множестве опорным элементом был выбран один из них.сравнивается с первым опорным элементом был или первым опорным элементом был первым опорным элементом был |
Mатожидание времени работы быстрой сортировки будет
.Улучшения
В случае повторяющихся неудачных разбиений опорным элементом, глубина рекурсии может достичь
, а время работы алгоритма . Существуют различные способы разбиения массива, направленные против худшего случая:- При выборе опорного элемента из данного диапазона случайным образом худший случай становится очень маловероятным и ожидаемое время выполнения алгоритма сортировки — .
- Выбирать опорным элементом средний из трех (первого, среднего и последнего элементов).
- Разбивать массив не на две, а на три части.
Оптимизация глубины рекурсии до O(logn) в худшем случае
Во избежание достижения опасной глубины рекурсии в худшем случае (или при приближении к нему) возможна модификация алгоритма, устраняющая одну ветвь рекурсии: вместо того, чтобы после разделения массива вызывать рекурсивно процедуру разделения для обоих найденных подмассивов, рекурсивный вызов делается только для меньшего подмассива, а больший обрабатывается в цикле в пределах этого же вызова процедуры. С точки зрения эффективности в среднем случае разницы практически нет: накладные расходы на дополнительный рекурсивный вызов и на организацию сравнения длин подмассивов и цикла — примерно одного порядка. Зато глубина рекурсии ни при каких обстоятельствах не превысит
, а в худшем случае вырожденного разделения она вообще будет не более 2 — вся обработка пройдёт в цикле первого уровня рекурсии.Источники информации
- Википедия — быстрая сортировка
- Wikipedia — Quicksort
- Т. Кормен, Ч. Лейзерсон, Р. Ривест: Алгоритмы: построение и анализ глава 7