Сортировка за O(N)-время


Случайно наткнулся на статью на java.dzone.comFast O(n) Integer Sorting Algorithm!

Всегда считал что O(n*log(n) ) это очень хороший показатель.  Например, стандартная реализация сортировки в Java 6 является слегка модифицированным вариантом merge sort, которая  соответственно дает время n*log(n).  Получить ультра ускорялку сортировки можно используя дополнительные хитрости: используя дополнительные знания о самих объектах которые мы сортируем + ограничения на возможные их значения.

Если верить википедии Гарольд Сьюард (Harold H. Seward) в 1954 году из MIT изобрел ультра крутой способ сортировки  – radix sort (поразрядная сортировка), а также разработал counting sort (сортировка подсчетом).
При такой сортировки мы не используем Comparator, т.е. сравнение и перестановка элементов как в обычной сортировки не происходит. С другой стороны, такой метод требует дополнительной памяти и
особо эффективен в тех случаях, когда мы сортируем большое количество натуральных чисел ограниченных по возможным принимаемым значениям. Например: массив из 1000000 целых чисел, которые принимают значения от 0 до 1000.

В исходной статье дается детальный график показывающий эффективность использования такой сортировки.

Далее привожу реализацию на Java алгоритм этой волшебной сортировки с моими комментариями.
Внимание! Такая сортировка работает для ПОЛОЖИТЕЛЬНЫХ int‘-ов.
Детальное описание алгоритма можно найти на википедии или нагуглить.
P.S.: У меня простое сравнение двух алгоритмов – через Arrays.sort() vs CountingSort.sort() дает выигрыш в пользу последнего (приблизительно в четыре раза быстрее на этом примере).

import java.util.Arrays;
import java.util.Random;

public class CountingSort {

    /**
     * @param array
     * @return
     */
    public static int[] sort(int[] array) {
        int min, max = min = array[0];
        // тупо находим максимальное и минимальное значение
        for (int i = 1; i < array.length; i++) {
            if (array[i] < min) {
                   min = array[i];
            }
            if (array[i] > max) {
                max = array[i];
            }
        }
        // понеслась
        return sort(array, min, max);
    }

    static int[] sort(int[] array, int min, int max) {
        // счетчик это такой массив в котором мы будем считать, как часто встречаются
        // числа в сортируемом массиве.
        // допустим массив равен = {0,2,1,5,1}, min = 0, max = 5
        // счетчик = count[0]....count[5]
        int[] count = new int[max - min + 1];
        // итак считаем...
        for (int i = 0; i < array.length; i++) {
            // подсчитываем сколько раз встречается число,
            // встретилось +1 к счетчику
            count[array[i] - min]++;
        }
        // например. count[0]=1, count[1]=2, count[3]=0,count[4]=0,count[5]=1
        int idx = 0;
        // теперь все готово
        // пробегаем по всему счетчику (от 0 до 5)
        // count[i] - показывает сколько раз встречается то или иное число
        for (int i = 0; i < count.length; i++) {
            // count[0]=1, значит array[0]=0;
            // count[1]=2, значит вставляем два раза array[1]=array[2]=1;
            // count[2]=1, опять только один раз. array[3]=2;
            // count[3]=0, значит ничего не вставляем и т.д.
            for (int j = 0; j < count[i]; j++) {
                array[idx++] = i + min;
            }
        }
        // ну собственно и всё
        return array;
    }

    public static void main(String[] args) {
        Random rnd = new Random();
        int arr1[] = new int[1024*1024];
        for (int i = 0; i < arr1.length; ++i) {
            arr1[i] = rnd.nextInt(1000) + 123;
        }
        int arr2[] = new int[arr1.length];
        System.arraycopy(arr1, 0, arr2, 0, arr1.length);
        long t1 = System.currentTimeMillis();
        sort(arr1);
        long t2 = System.currentTimeMillis();
        Arrays.sort(arr2);
        long t3 = System.currentTimeMillis();
        System.out.printf("counting sort: %d merge sort: %d \r\n", (t2-t1), (t3-t2));
        //
        System.out.println(Arrays.equals(arr1, arr2));
    }
}
Любое использование либо копирование материалов или подборки материалов сайта, элементов дизайна и оформления допускается лишь с разрешения правообладателя и только со ссылкой на источник: programador.ru

Телеграм канал: @prgrmdr
Почта для связи: vit [at] programmisty.com