Быстрая сортировка (англ. quicksort), часто
называемая по имени реализации в стандартной библиотеке языка Си —
широко известный алгоритм
сортировки, разработанный английским информатиком Чарльзом Хоаром в МГУ в 1960 году. Один из быстрых известных универсальных алгоритмов
сортировки массивов (в среднем O(n log n) обменов при упорядочении n элементов), хотя и имеющий ряд
недостатков.
Краткое описание алгоритма
·
выбрать элемент, называемый опорным.
·
сравнить все остальные элементы с опорным, на основании сравнения разбить
множество на три — «меньшие опорного», «равные» и «большие», расположить
их в порядке меньшие-равные-большие.
·
повторить рекурсивно для «меньших» и «больших».
Примечание: на практике обычно разделяют
сортируемое множество не на три, а на две части: например, «меньшие опорного» и
«равные и большие». Такой подход в общем случае оказывается эффективнее, так
как для осуществления такого разделения достаточно одного прохода по
сортируемому множеству и однократного обмена лишь некоторых выбранных
элементов.
QuickSort является существенно улучшенным
вариантом алгоритма сортировки с помощью прямого обмена (его варианты известны
как «Пузырьковая сортировка» и «Шейкерная сортировка»), известного, в том
числе, своей низкой эффективностью. Принципиальное отличие состоит в том, что
после каждого прохода элементы делятся на две независимые группы. Любопытный
факт: улучшение самого неэффективного прямого метода сортировки дало в
результате эффективный улучшенный метод.
·
Лучший случай. Для этого алгоритма самый лучший
случай — если в каждой итерации каждый из подмассивов делился бы на два равных
по величине массива. В результате количество сравнений, делаемых быстрой
сортировкой, было бы равно значению рекурсивного выражения CN =
2CN/2+N, что в явном выражении дает примерно N lg N сравнений. Это
дало бы наименьшее время сортировки.
·
Среднее. Даёт в среднем O(n log n)
обменов при упорядочении n элементов. В реальности именно
такая ситуация обычно имеет место при случайном порядке элементов и выборе
опорного элемента из середины массива либо случайно.
На практике (в случае, когда обмены являются более затратной операцией, чем сравнения) быстрая сортировка значительно быстрее, чем другие алгоритмы с оценкой O(n lg n), по причине того, что внутренний цикл алгоритма может быть эффективно реализован почти на любой архитектуре. 2CN/2 покрывает расходы по сортировке двух полученных подмассивов; N — это стоимость обработки каждого элемента, используя один или другой указатель. Известно также, что примерное значение этого выражения равно CN = N lg N.
На практике (в случае, когда обмены являются более затратной операцией, чем сравнения) быстрая сортировка значительно быстрее, чем другие алгоритмы с оценкой O(n lg n), по причине того, что внутренний цикл алгоритма может быть эффективно реализован почти на любой архитектуре. 2CN/2 покрывает расходы по сортировке двух полученных подмассивов; N — это стоимость обработки каждого элемента, используя один или другой указатель. Известно также, что примерное значение этого выражения равно CN = N lg N.
·
Худший случай. Худшим случаем, очевидно, будет
такой, при котором на каждом этапе массив будет разделяться на вырожденный
подмассив из одного опорного элемента и на подмассив из всех остальных
элементов. Такое может произойти, если в качестве опорного на каждом этапе
будет выбран элемент либо наименьший, либо наибольший из всех обрабатываемых.
Худший случай даёт O(n²) обменов. Но количество обменов и, соответственно, время работы — это не самый большой его недостаток. Хуже то, что в таком случае глубина рекурсии при выполнении алгоритма достигнет n, что будет означать n-кратное сохранение адреса возврата и локальных переменных процедуры разделения массивов. Для больших значений n худший случай может привести к исчерпанию памяти во время работы алгоритма. Впрочем, на большинстве реальных данных можно найти решения, которые минимизируют вероятность того, что понадобится квадратичное время.
Худший случай даёт O(n²) обменов. Но количество обменов и, соответственно, время работы — это не самый большой его недостаток. Хуже то, что в таком случае глубина рекурсии при выполнении алгоритма достигнет n, что будет означать n-кратное сохранение адреса возврата и локальных переменных процедуры разделения массивов. Для больших значений n худший случай может привести к исчерпанию памяти во время работы алгоритма. Впрочем, на большинстве реальных данных можно найти решения, которые минимизируют вероятность того, что понадобится квадратичное время.
·
·
//алгоритм на языке java и с//с++
·
public static void qSort(int[] A, int low, int high) {
·
int i = low;
·
int j = high;
·
int x = A[(low+high)/2]; // x - опорный элемент посредине между
low и high
·
do {
·
while(A[i] < x) ++i; // поиск элемента для переноса в старшую
часть
·
while(A[j] > x) --j; // поиск элемента для переноса в младшую
часть
·
if(i <= j){
·
// обмен элементов местами:
·
int temp = A[i];
·
A[i] = A[j];
·
A[j] = temp;
·
// переход к следующим элементам:
·
i++; j--;
·
}
·
} while(i < j);
·
if(low < j) qSort(A, low, j);
·
if(i < high) qSort(A, i, high);
·
}
Анализ
сложности – Королев-Миков страницы 122!!!