Сортировки сравнением (Comparison sorts)
Сортировки сравнением имеют минимальную сложность O(n*lg n).
Сортировка вставками (Insert sort)
Сложность: O(n2)
На каждом шаге алгоритма выбираем очередной элемент входных данных и сдвигаем его влево до тех пор, пока слева от него не останутся элементы, меньшие чем он. Метод выбора очередного элемента из исходного массива произволен; может использоваться практически любой алгоритм выбора. Обычно (и с целью получения устойчивого алгоритма сортировки), элементы выбираются по порядку их появления во входном массиве.
Преимущества: практически не требует выделения памяти (всего O(1)); алгоритм эффективен при малом размере входного массива.
Недостатки: неэффективен на большом размере входного массива (более 100).
Алгоритм:
Вход: массив A, состоящий из элементов A[1], A[2], ..., A[n]
for i = 2, 3, ..., n:
key = A[i]
j = i - 1
while j > 0 and A[j] > key:
A[j + 1] = A[j]
j = j - 1
A[j + 1] = key
Реализация на Java:
//insertion sort
public static void insertionSort(int[] arr) {
int i, j, tmp;
//двигаемся вперед по массиву
for (i = 1; i < arr.length; i++) {
j = i;
//двигаем элемент назад, пока он больше предыдущих
while (j > 0 && arr[j - 1] > arr[j]) {
tmp = arr[j];
arr[j] = arr[j - 1];
arr[j - 1] = tmp;
j--;
}
}
}
Сортировка слиянием (Merge sort)
Сложность: O(n*lg n)
Этот алгоритм использует парадигму "Разделяй и властвуй". Сортируемая последовательность, состоящая из n элементов, разбивается на две меньшие последовательности. Рекурсивно сортируем каждую из полученных последовательностей тем же способом. Когда две меньшие последовательности отсортированы, производится слияние, поочередно выбирая из них наименьший член.
Преимущества: алгоритм эффективен при большом размере входного массива (более 1000).
Недостатки: требует выделения O(n) памяти
Алгоритм:
function mergesort(m)
var list left, right, result
if length(m) ≤ 1
return m
else
middle = length(m) / 2
for each x in m up to middle
add x to left
for each x in m after middle
add x to right
left = mergesort(left)
right = mergesort(right)
result = merge(left, right)
return result
end if
function merge(left,right)
var list result
while length(left) > 0 and length(right) > 0
if first(left) ≤ first(right)
append first(left) to result
left = rest(left)
else
append first(right) to result
right = rest(right)
end if
if length(left) > 0
append left to result
if length(right) > 0
append right to result
return result
Реализация на Java:
//merge sort
public static int[] mergeSort(int[] arr) {
if (arr.length <= 1) return arr;
int mid = arr.length / 2;
int[] left = new int[mid];
int[] right = new int[mid + arr.length%2];
int j = 0;
for (int i = 0; i < arr.length; i++) {
if (i < arr.length / 2) {
left[i] = arr[i];
} else {
right[j++] = arr[i];
}
}
return Merge(mergeSort(left), mergeSort(right));
}
public static int[] Merge(int[] left, int[] right) {
int a = 0, b = 0;
int[] merged = new int[left.length + right.length];
//забиваем отсортированный массив из левой и правой частей
for (int i = 0; i < left.length + right.length; i++) {
//поочередно берем меньший член из крайних левого и правого
if (b < right.length && a < left.length)
if (left[a] > right[b] && b < right.length)
merged[i] = right[b++];
else
merged[i] = left[a++];
else
if (b < right.length)
merged[i] = right[b++];
else
merged[i] = left[a++];
}
return merged;
}
Пирамидальная сортировка (Heap sort)
Сложность: O(n*lg n)
Сортировка пирамидой использует сортирующее дерево. Алгоритм заключается в выстраивании элементов массива в сортирующее дерево. Итерационно удаляем элементы из корня под одному за раз и перестраиваем дерево. То есть на первом шаге обмениваем Array[1] и Array[n], преобразовываем Array[1], Array[2], … , Array[n-1] в сортирующее дерево. Затем переставляем Array[1] и Array[n-1], преобразовываем Array[1], Array[2], … , Array[n-2] в сортирующее дерево. Процесс продолжается до тех пор, пока в сортирующем дереве не останется один элемент. Тогда Array[1], Array[2], … , Array[n] — упорядоченная последовательность.
Преимущества: практически не требует выделения памяти (всего O(1))
Недостатки: Из-за сложности алгоритма выигрыш получается только на больших n (более 1000)
Алгоритм:
function build_max_heap(A)
for i = length(A) downto 2
do Обменять A[1] на A[i]
heap_size[A] = heap_size[A] - 1
max_heapify(A,1)
function max_heapify(A,i)
l = left(i)
r = right(i)
if l <= heap_size(A) and A[l] > A[i]
then largest = l
else largest = i
if r <= heap_size(A) and A[r] > A[largest]
then largest = r
if largest != i
then Обменять A[i] на A[largest]
max_heapify(A, largest)
Реализация на Java:
public static void heapSort(int[] arr) {
for(int i = arr.length; i > 1; i--){
buildMaxHeap(arr, i - 1);
}
}
public static void buildMaxHeap(int[] arr, int max_index){
int i, o;
int leftChild, rightChild, maxChild, root, temp;
root = (max_index -1)/2;
//обходим дерево в поисках максимального члена (ранее отсортированные максимальные игнорируем)
for(i = root; i >= 0; i--){
leftChild = (2*i) + 1;
rightChild = (2*i) + 2;
if((leftChild <= max_index) && (rightChild <= max_index)){
if(arr[rightChild] >= arr[leftChild])
maxChild = rightChild;
else
maxChild = leftChild;
} else{
if(rightChild > max_index)
maxChild = leftChild;
else
maxChild = rightChild;
}
//передвигаем максимальный член вверх по пирамиде (начало массива)
if(arr[i] < arr[maxChild]){
temp = arr[i];
arr[i] = arr[maxChild];
arr[maxChild] = temp;
}
}
//убираем максимальный член в конец массива
temp = arr[0];
arr[0] = arr[max_index];
arr[max_index] = temp;
}
Быстрая сортировка (Quick sort)
Сложность: средняя O(n*lg n), максимальная O(n2)
Еще одна сортировка, основанная на парадигме "Разделяй и властвуй". Массив A[p..r] разбивается (путем переупорядочивания его элементов) на два подмассива A[p..q-1] и A[q+1..r]. Каждый элемент первого подмассива не превышает каждый елемент второго. Индекс q вычисляется в ходе процедуры разбиения и называется опорным элементом. Подмассивы сортируются путем рекурсивного вызова процедуры быстрой сортировки. После этого весь массив оказывается отсортированным.
Преимущества: при качественной реализации один из самых быстродействующих алгоритмов на практике; требует лишь O(lg n) дополнительной памяти
Недостатки: при неудачном выборе опорного элемента время вычисления деградирует к O(n2).
Алгоритм:
для сортировки всего списка вызов выглядит quick_sort(A,1,length[A])
function quick_sort(A,p,r)
if p < r
then q = partition(A,p,r)
quick_sort(A,p,q-1)
quick_sort(A,q+1,r)
function partition(A,p,r)
x = A[r]
i = p - 1
for i = p to r - 1
do if A[j] <= x
then i = i + 1
Обменять A[i] на A[j]
Обменять A[i + 1] на A[r]
return i + 1
Реализация на Java: //quick sort
public static void quickSort(int[] arr, int low, int high) {
int i = low;
int j = high;
//выбираем середину в качестве опорного элемента
int mid = arr[(low + high)/2];
//сдвигаем меньшие члены влево от опорного, а большие вправо
do {
while (arr[i] < mid)
++i;
while (mid < arr[j])
--j;
if ( i <= j ) {
if( i < j ) {
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
++i;
--j;
}
} while (i <= j);
//теперь также сортируем левую и правую часть
if (low < j)
quickSort(arr, low, j);
if (i < high)
quickSort(arr, i, high);
}
Сортировки за линейное время
Как видно из названия, сложность следующих сортировок O(n)
Сортировка подсчетом (Counting sort)
Идея алгоритма заключается в том, чтобы для каждого входного элемента определить количество элементов, которые меньше него. С помощью этой информации элемент можно разместить на соответствующей позиции выходного массива.
Достоинства: достаточно быстр
Недостатки: элементы входного массива должны быть в интервале от 0 до k, где k - натуральное число; требует выделения памяти
Алгоритм:
function counting_sort
for i = 0 to k - 1
C[i] = 0
for i = 0 to n - 1
C[A[i]] = C[A[i]] + 1
b = 0
for j = 0 to k - 1
for i = 0 to C[j] - 1
A[b] = j
b = b + 1
Реализация на Java:
//counting sort
public static void countingSort(int[] arr, int max) {
int i, j, b = 0;
int[] c = new int[max];
//забьем вспомогательный массив нулями (его индексы соответствуют исходным значениям)
for (i = 0; i < max; i++) c[i] = 0;
//забьем вспомогательный массив значениями, равными количеству элементов, меньших данного значения индекса
for (i = 0; i < arr.length; i++) {
c[arr[i]] = c[arr[i]] + 1;
b = 0;
}
//забьем на основе данных из вспомогательного массива выходной массив
for (j = 0; j < max; j++) {
for (i = 0; i < c[j]; i++) {
arr[b] = j;
b = b + 1;
}
}
}
Поразрядная сортировка (Radix sort)
Вариант сортировки с младших разрядов least significant digit (LSD). Сначала производится сортировка по младшему разряду, потом по следующему, и так далее, пока дело не дойдет до старшего разряда. Но существует и второй вариант - сортировка по старшему разряду (most significant digit (MSD)).
Недостатки: хотя для этой сортировки нужно меньшее количество итераций прохода по элементам, чем для сортировок сравнения, каждая итерация может длиться значительно дольше.
Алгоритм:
function radix_sort
for i = 1 to d
do Устойчивая сортировка массива A по i-ой цифре
Реализация на Java:
//radix sort
public static void radixSort(int[] arr) {
int cnt[][] = new int[4][];
int b[];
int i, j;
int a_len = arr.length;
if (a_len < 2) {
// массив длиной 1 элемент не нужно сортировать :)
return;
}
// инициализируем счетчик [cnt]
for (j = 0; j < 4; j++) {
cnt[j] = new int[257];
for (i = 0; i < 257; i++) cnt[j][i] = 0;
}
// выделяем память под копию сортируемого массива
b = new int[a_len];
// подсчитываем количество элементов для каждого значения j-го разряда
for (i = 0; i < a_len; i++) {
for (j = 0; j < 4; j++) {
cnt[j][1 + ((arr[i] >>> (8 * j)) & 0xff)]++;
}
}
for (j = 0; j < 4; j++) {
/*
вычисляем позиции cnt[i], начиная с которых будут располаться элементы
с соответствующим значением j-го разряда
*/
for (i = 1; i < 256; i++) cnt[j][i] += cnt[j][i - 1];
// расставляем элементы из массива a в массив b в указанном порядке
for (i = 0; i < a_len; i++) {
b[cnt[j][(arr[i] >>> (8 * j)) & 0xff]++] = arr[i];
}
// копируем массив b на место массива a
for (i = 0; i < a_len; i++) arr[i] = b[i];
}
}
Блочная (карманная) сортировка (Bucket sort)
Идея, лежащая в основе алгоритма заключается в том, чтобы разбить интервал [0,1) на n одинаковых интервалов, а затем распределить по этим интервалам n входных величин. Поскольку входные элементы равномерно распределены, можно предположить, что в каждый интервал попадет не много элементов. Для получения выходной последовательности, надо провести сортировку в каждом интервале, а затем последовательно перечислить элементы в каждом из них.
Недостатки: ожидаемое время работы алгоритма линейно зависит от количества входных данных только при равномерном распределении входных элементов на отрезке.
Алгоритм:
function bucket-sort(A, n) is
buckets ← новый массив из n пустых элементов
for i = 0 to (length(A)-1) do
вставить A[i] в конец массива buckets[nA[i]]
for i = 0 to n - 1 do
next-sort(buckets[i])
return Конкатенация массивов buckets[0], ..., buckets[n-1]
Реализация на Java:
//bucket sort
public static void bucketSort(int[] arr) {
//поиск максимального элемента массива
int maxValue = arr[0];
for (int k = 1; k < arr.length; k++) {
if (arr[k] > maxValue)
maxValue = arr[k];
}
int i, j;
//создадим вспомогательный массив
int[] bucket = new int[maxValue + 1];
//распределим числа по карманам
for(i = 0; i < arr.length; i++ ) {
bucket[arr[i]]++;
}
//отсортируем числа в карманах используя сортировку подсчетом
for(i = 0, j = 0; i < bucket.length; i++) {
for(; bucket[i]>0; (bucket[i])--) {
arr[j++] = i;
}
}
}
ну и напоследок выложу код для запуска всех вышеперечисленных сортировок, может кому-то он пригодится: public class Sorting {
public static void main(String[] args) {
String[] types = {"insertion","merge","heap","quick", "counting", "radix", "bucket"};
sort(types);
}
private static void sort(String[] types) {
for (String type: types) {
int[] arr = {8,4,7,2,7,0,32,17,45,43,32,4,38,4,89,3,6,7,86,3,545,6,7,2,54};
int[] arr2 = {8,24,7,2,7,0,22,17,15,13,12,4,18,4,19,3,6,7,16,3,15,6,7,2,14};
if (type.equals("insertion")) {
insertionSort(arr);
} else if (type.equals("merge")) {
arr = mergeSort(arr);
} else if (type.equals("heap")) {
heapSort(arr);
} else if (type.equals("quick")) {
quickSort(arr, 0, arr.length -1);
} else if (type.equals("counting")) {
countingSort(arr, 1000);
} else if (type.equals("radix")) {
radixSort(arr);
} else if (type.equals("bucket")) {
bucketSort(arr2);
}
System.out.println("\n"+ type + "Sorted: ");
if (!type.equals("bucket")) {
for (int i: arr) System.out.print(i + ",");
} else {
for (int i: arr2) System.out.print(i + ",");
}
}
}
... //here is the code of sorts
}
В bucketSort есть маленькая ошибка, размерность массива bucket должна совпадать с максимальным числом в массиве arr +1, иначе будет ArrayIndexOutOfBoundsException. Ну и естественно можно оптимизировать данный алгоритм , используя тот же HashMap для хранения бакетсов и хитрую функцию для для заполнения бакетсов. А в целом статья хорошая. Спасибо
ОтветитьУдалитьПро размерность массива - точно подмечено. Поправил, Спасибо.
УдалитьЗам при заполнение массива c , каждый раз обнуляется переменная b ?
ОтветитьУдалитьpublic static void countingSort(int[] arr, int max) {
int i, j, b = 0;
int[] c = new int[max];
for (i = 0; i < max; i++) c[i] = 0;
for (i = 0; i < arr.length; i++) {
c[arr[i]] = c[arr[i]] + 1;
b = 0;
}
for (j = 0; j < max; j++) {
for (i = 0; i < c[j]; i++) {
arr[b] = j;
b = b + 1;
}
}
}
Действительно, делать присваивание b=0 в цикле излишне. Его можно вынести за пределы цикла:
Удалитьpublic static void countingSort(int[] arr, int max) {
int i, j, b = 0;
int[] c = new int[max];
for (i = 0; i < max; i++) c[i] = 0;
for (i = 0; i < arr.length; i++) {
c[arr[i]] = c[arr[i]] + 1;
}
b = 0;
for (j = 0; j < max; j++) {
for (i = 0; i < c[j]; i++) {
arr[b] = j;
b = b + 1;
}
}
}
Сортировка вставками (Insert sort)
ОтветитьУдалитьРешил сам побырому по псевдокоду накидать, увы не сортировался первый элемент
Исправил.
for (int i = 1; i < array.length; i++) {
int key = array[i];
int j = i - 1;
while (j >= 0 && array[j] > key) {
array[j + 1] = array[j];
array[j] = key;
j--;
}
}
Если в псевдокоде while j > 0 and A[j] > key: написать j>=0
и писать на джаве по псевдокоду то будет все работать.
Пример выше, но у меня i = 1 если поставить i = 0 так же будет рабоатать.
Сортировка вставками (Insert sort)
ОтветитьУдалитьТочно по псевдокоду.
for (int i = 0; i < array.length; i++) {
int key = array[i];
int j = i - 1;
while (j >= 0 && array[j] > key) {
array[j + 1] = array[j];
j = j - 1;
array[j+1] = key;
}
}
Я имел ввиду добавить только знак "="
в псевдокоде что бы выглядело так while j >= 0 and A[j] > key: