Когда мы должны использовать сортировку Radix? - PullRequest
40 голосов
/ 10 ноября 2010

Кажется, что сортировка Radix имеет очень хорошую среднюю производительность, т.е.

но кажется, что большинство людей все еще используют быструю сортировку, не так ли?

Ответы [ 11 ]

27 голосов
/ 10 ноября 2010

Radix-сортировку сложнее обобщить, чем большинству других алгоритмов сортировки.Требуются ключи фиксированного размера и какой-то стандартный способ разбиения ключей на части.Таким образом, он никогда не попадет в библиотеки.

17 голосов
/ 10 ноября 2010

Отредактировано по вашим комментариям:

  • Радикальная сортировка применяется только к целым числам, строкам фиксированного размера, с плавающей запятой и к предикатам сравнения "меньше чем", "больше чем" или "лексикографический порядок", тогда как сортировки сравнения могут учитывать различные порядки.
  • k может быть больше, чем log N.
  • Быстрая сортировка может быть выполнена на месте, радикальная сортировка становится менее эффективной.
16 голосов
/ 09 ноября 2013

Другие ответы здесь ужасны, они не дают примеров того, когда на самом деле используется радикальная сортировка .

Пример - создание «массива суффиксов» с использованием асимметричного DC3алгоритм (Керккяйнен-Сандерс-Буркхардт).Алгоритм является линейным по времени, если алгоритм сортировки является линейным по времени, а радикальная сортировка здесь необходима и полезна, потому что ключи короткие по построению (3 кортежа целых чисел).

9 голосов
/ 10 ноября 2010

Если у вас нет огромного списка или чрезвычайно маленьких ключей, log (N) обычно меньше k, оно редко намного выше.Поэтому выбор алгоритма сортировки общего назначения с O (N log N) средней производительностью не обязательно хуже, чем с использованием радикальной сортировки.

Исправление : как @Mehrdad указал в комментарияхприведенный выше аргумент неверен: либо размер ключа постоянен, либо радикальная сортировка равна O (N), либо размер ключа равен k, а затем быстрая сортировка равна O (k N log N).Таким образом, теоретически у радикальной сортировки действительно лучше асимптотическое время выполнения.

На практике во время выполнения будут доминировать такие термины, как:

  • радикальная сортировка: c1 k N

  • быстрая сортировка: c2 k N log (N)

, где c1 >> c2, потому что "извлечение" битов из более длинного ключа обычнодорогостоящая операция, включающая сдвиги битов и логические операции (или, по крайней мере, доступ к памяти без выравнивания), в то время как современные процессоры могут сравнивать ключи с 64, 128 или даже 256 битами за одну операцию.Таким образом, для многих распространенных случаев, если N не является гигантским, c1 будет больше, чем c2 log (N)

8 голосов
/ 28 марта 2013

при n> 128, мы должны использовать RadixSort

при сортировке int32s я выбираю radix 256, поэтому k = log (256, 2 ^ 32) = 4, что значительно меньше log (2, n)

и в моем тесте радикальная сортировка в 7 раз быстрее быстрой сортировки в лучшем случае.

public class RadixSort {
    private static final int radix=256, shifts[]={8,16,24}, mask=radix-1;
    private final int bar[]=new int[radix];
    private int s[] = new int[65536];//不使用额外的数组t,提高cpu的cache命中率

    public void ensureSort(int len){
        if(s.length < len)
            s = new int[len];
    }   

    public void sort(int[] a){
        int n=a.length;
        ensureSort(n);
        for(int i=0;i<radix;i++)bar[i]=0;
        for(int i=0;i<n;i++)bar[a[i]&mask]++;//bar存放了桶内元素数量
        for(int i=1;i<radix;i++)bar[i]+=bar[i-1];//bar存放了桶内的各个元素在排序结果中的最大下标+1
        for(int i=0;i<n;i++)s[--bar[a[i]&mask]]=a[i];//对桶内元素,在bar中找到下标x=bar[slot]-1, 另s[x]=a[i](同时--bar[slot]将下标前移,供桶内其它元素使用)

        for(int i=0;i<radix;i++)bar[i]=0;
        for(int i=0;i<n;i++)bar[(s[i]>>8)&mask]++;
        for(int i=1;i<radix;i++)bar[i]+=bar[i-1];
        for(int i=n-1;i>=0;i--)a[--bar[(s[i]>>8)&mask]]=s[i];//同一个桶内的元素,低位已排序,而放入t中时是从t的大下标向小下标放入的,所以应该逆序遍历s[i]来保证原有的顺序不变

        for(int i=0;i<radix;i++)bar[i]=0;
        for(int i=0;i<n;i++)bar[(a[i]>>16)&mask]++;
        for(int i=1;i<radix;i++)bar[i]+=bar[i-1];
        for(int i=n-1;i>=0;i--)s[--bar[(a[i]>>16)&mask]]=a[i];//同一个桶内的元素,低位已排序,而放入t中时是从t的大下标向小下标放入的,所以应该逆序遍历s[i]来保证原有的顺序不变

        for(int i=0;i<radix;i++)bar[i]=0;
        for(int i=0;i<n;i++)bar[(s[i]>>24)&mask]++;
        for(int i=129;i<radix;i++)bar[i]+=bar[i-1];//bar[128~255]是负数,比正数小
        bar[0] += bar[255];
        for(int i=1;i<128;i++)bar[i]+=bar[i-1];     
        for(int i=n-1;i>=0;i--)a[--bar[(s[i]>>24)&mask]]=s[i];//同一个桶内的元素,低位已排序,而放入t中时是从t的大下标向小下标放入的,所以应该逆序遍历s[i]来保证原有的顺序不变      
    }
}
8 голосов
/ 18 июля 2011

Радикальная сортировка занимает O (k * n) время. Но вы должны спросить, что такое K. K - это «количество цифр» (немного упрощенно, но в основном что-то в этом роде).

Итак, сколько у вас цифр? Довольно ответ, больше чем log (n) (log, используя «размер цифры» в качестве основы), что делает алгоритм Radix O (n log n).

Почему это? Если у вас меньше, чем log (n) цифр, значит, у вас меньше n возможных чисел. Следовательно, вы можете просто использовать «сортировку по счету», которая занимает O (n) времени (просто посчитайте, сколько из каждого числа у вас есть). Поэтому я предполагаю, что у вас есть больше чем k> log (n) цифр ...

Вот почему люди так редко используют сортировку по Radix. Хотя бывают случаи, когда его стоит использовать, в большинстве случаев быстрая сортировка намного лучше.

3 голосов
/ 20 октября 2012

k = "длина самого длинного значения в массиве для сортировки"

n = "длина массива"

O (k * n) = "работает в худшем случае"

k * n = n ^ 2 (если k = n)

поэтому при использовании сортировки Radix убедитесь, что «наибольшее целое число короче размера массива» или наоборот. Тогда ты победишь быструю сортировку!

Недостатком является то, что в большинстве случаев вы не можете определить, насколько большими становятся целые числа, но если у вас фиксированный диапазон чисел, сортировка по радиксам должна быть подходящим вариантом.

2 голосов
/ 04 января 2018

Radix-сортировка не является сортировкой на основе сравнения и может сортировать только числовые типы, такие как целые числа (включая адреса указателей) и с плавающей запятой, и немного затруднительно переносить поддержку с плавающей запятой.

Этовероятно, потому что он имеет такой узкий диапазон применимости, что многие стандартные библиотеки предпочитают его опускать.Он даже не может позволить вам предоставить свой собственный компаратор, поскольку некоторые люди могут даже не захотеть даже сортировать целые числа напрямую, а использовать целые числа как индексы для чего-то еще, что будет использоваться в качестве ключа для сортировки, например, сортировки на основе сравнения позволяют всемэта гибкость, так что, вероятно, это просто случай, когда предпочитают обобщенное решение, удовлетворяющее 99% ежедневных потребностей людей, вместо того, чтобы идти в ногу со временем, чтобы удовлетворить этот 1%.

Тем не менее, несмотря на узкую применимость,в моем домене я нахожу больше пользы для радикальных сортировок, чем для интросортов или быстрых сортировок.Я нахожусь в этом 1% и почти никогда не работаю, скажем, со строковыми ключами, но часто нахожу варианты использования для чисел, которые выигрывают от сортировки.Это потому, что моя кодовая база вращается вокруг индексов для сущностей и компонентов (система сущностей-компонентов), а также для таких вещей, как индексированные сетки, и существует огромное количество числовых данных.

В результате радикальная сортировка становится полезной для всех видов.вещей в моем случае.Одним из распространенных примеров в моем случае является устранение дублирующих индексов.В этом случае мне не нужно, чтобы результаты сортировались, но часто радикальная сортировка может удалять дубликаты быстрее, чем альтернативы.

Другой - это нахождение, скажем, медианного разбиения для дерева kd вдоль заданногоизмерение.При радикальной сортировке значений точки с плавающей точкой для данного измерения я быстро получаю срединное положение за линейное время, чтобы разделить узел дерева.

Другим является сортировка по глубине примитивов более высокого уровня по z дляполу-правильная альфа-прозрачность, если мы не собираемся делать это в фрагментном шейдере.Это также относится к графическим интерфейсам и программному обеспечению векторной графики для элементов z-порядка.

Еще один последовательный доступ с поддержкой кэша с использованием списка индексов.Если индексы просматриваются много раз, то часто улучшается производительность, если я сортирую их заранее, так что обход выполняется в последовательном порядке, а не в случайном порядке.Последний может перемещаться по памяти взад-вперед, высвобождая данные из строк кэша, только для повторной загрузки одной и той же области памяти в пределах одного и того же цикла.Когда я сначала сортирую индексы перед повторным доступом к ним, это перестает происходить, и я могу значительно уменьшить потери в кеше.На самом деле это мое наиболее распространенное использование для сортировки по основанию, и это является ключом к тому, что моя система ECS поддерживает кеш, когда системы хотят получить доступ к объектам с двумя или более компонентами.

В моем случае у меня есть многопоточная сортировка по основаниюиспользовать довольно часто.Некоторые тесты:

--------------------------------------------
- test_mt_sort
--------------------------------------------
Sorting 1,000,000 elements 32 times...

mt_radix_sort: {0.234000 secs}
-- small result: [ 22 48 59 77 79 80 84 84 93 98 ]

std::sort: {1.778000 secs}
-- small result: [ 22 48 59 77 79 80 84 84 93 98 ]

qsort: {2.730000 secs}
-- small result: [ 22 48 59 77 79 80 84 84 93 98 ]

Я могу усреднить что-то вроде 6-7 мс, чтобы один раз отсортировать миллион чисел на моем извращенном оборудовании, что не так быстро, как хотелось бы, поскольку 6-7 миллисекунд все еще могут бытьиногда замечается пользователями в интерактивном контексте, но все же намного лучше, чем 55-85 мс, как в случае C ++ std::sort или C qsort, что определенно приведет к очень очевидным сбоям в частоте кадров.Я даже слышал о людях, использующих радикальные сортировки с использованием SIMD, хотя я понятия не имею, как им это удалось.Я не достаточно умен, чтобы придумать такое решение, хотя даже мой маленький наивный метод radix довольно хорош по сравнению со стандартными библиотеками.

2 голосов
/ 23 января 2015

Вот ссылка, которая сравнивает быструю сортировку и radixsort:

Является ли сортировка по основанию быстрее, чем быстрая сортировка для целочисленных массивов? (да, это 2-3x)

Вотдругая ссылка, которая анализирует время выполнения нескольких алгоритмов:

Вопрос типа :

, который быстрее на тех же данных;сортировка O (n) или сортировка O (nLog (n))?

Ответ: Это зависит.Это зависит от количества сортируемых данных.Это зависит от оборудования, на котором он работает, и от реализации алгоритмов.

0 голосов
/ 19 октября 2016

Один из примеров - сортировка очень большого набора или массива целых чисел. Радикальная сортировка и любые другие типы распределения сортировки чрезвычайно быстры, поскольку элементы данных в основном помещаются в массив очередей (максимум 10 очередей для радикальной сортировки LSD) и сопоставляются с другим местоположением индекса тех же входных данных, которые должны быть отсортированы. Вложенных циклов нет, поэтому алгоритм имеет тенденцию вести себя более линейно, так как число сортируемых входных целых чисел становится значительно больше. В отличие от других методов сортировки, таких как крайне неэффективный метод bubbleSort, сортировка по основанию не реализует операции сравнения для сортировки. Это простой процесс перераспределения целых чисел в разные позиции индекса до тех пор, пока ввод не будет окончательно отсортирован. Если вы хотите самостоятельно протестировать сортировку с помощью радиуса LSD, я написал ее и сохранил на github, которую можно легко протестировать на онлайновой js ide, такой как песочница для eloquent javascript. Не стесняйтесь поиграть с ним и посмотреть, как он ведет себя с различными числами n. Я протестировал до 900 000 несортированных целых чисел со временем выполнения <300 мс. Вот ссылка, если вы хотите поиграть с ней. </p>

https://gist.github.com/StBean/4af58d09021899f14dfa585df6c86df6

...