Когда следует использовать сортировку Radix?

Кажется, сортировка Radix имеет очень хорошую производительность в среднем регистре, то есть O (kN): http://en.wikipedia.org/wiki/Radix_sort

Тем не менее, похоже, что большинство людей все еще используют быструю сортировку - почему это?


person Howard    schedule 10.11.2010    source источник
comment
Большинство людей используют процедуру сортировки, предоставляемую их предпочтительной структурой, даже не заботясь об алгоритме.   -  person Doc Brown    schedule 10.11.2010
comment
Сортировка Radix не подходит для другого типа данных, но если вы хотите отсортировать unsigned int и хотите, чтобы сортировка выполнялась на многоядерном процессоре, таком как GPU, сортировка radix выполняется быстрее.   -  person tintin    schedule 12.10.2014


Ответы (12)


Быстрая сортировка имеет среднее значение O (N logN), но также имеет наихудший случай O (N ^ 2), поэтому даже из-за того, что в большинстве практических случаев он не дойдет до N ^ 2, всегда существует риск того, что ввод будет для вас в "плохом состоянии". Этот риск не существует при сортировке по основанию счисления. Я думаю, что это дает большое преимущество радииксной сортировке.

person Guy Nir    schedule 18.11.2010
comment
Вряд ли это главное преимущество. Другие сортировки на основе сравнения (например, heapsort или mergesort) не имеют такого плохого поведения в худшем случае, как быстрая сортировка. - person Eldritch Conundrum; 19.12.2013
comment
худший сценарий для быстрой сортировки на самом деле не является аргументом, потому что люди обычно используют рандомизированную быструю сортировку, то есть перетасовывают входные данные перед их фактической сортировкой. это практически исключает шанс иметь время работы N ^ 2. - person nburk; 09.04.2015
comment
Об этом позаботится Introsort, использующий быструю сортировку. Это не аргумент. - person user541686; 09.04.2015

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

person Mark Ransom    schedule 10.11.2010

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

Примером может служить создание массива суффиксов с использованием алгоритма перекоса DC3 (Kärkkäinen-Sanders-Burkhardt). Алгоритм является линейным только в том случае, если алгоритм сортировки является линейным, и сортировка по основанию системы координат необходима и полезна здесь, потому что ключи короткие по конструкции (3-кортежи целых чисел).

person user541686    schedule 09.11.2013
comment
Полностью согласен. Никаких упоминаний о том, когда он на самом деле используется, и никаких реальных тестов, которые сравнивают два алгоритма. - person Ivan Š; 23.01.2015

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

  • Радиксная сортировка применяется только к целым числам, строкам фиксированного размера, с плавающей запятой и к предикатам сравнения «меньше чем», «больше чем» или «лексикографический порядок», тогда как сортировки сравнения могут соответствовать разным порядкам.
  • k может быть больше log N.
  • Быстрая сортировка может выполняться на месте, радикальная сортировка становится менее эффективной.
person Alexandre C.    schedule 10.11.2010
comment
Быстрая сортировка может выполняться на месте - так же как и двоичная сортировка по основанию, хотя это увеличивает вероятность того, что k больше, чем log N. - person Steve Jessop; 10.11.2010
comment
Ваш первый пункт не совсем верен - сортировку Radix можно легко применить к строкам фиксированной длины. И предикат сравнения необходим независимо от того, какой алгоритм сортировки вы используете. - person Mark Ransom; 10.11.2010
comment
Радиксная сортировка применяется только к целым числам: Почему? Я всегда думал, что если вы сортируете по битам экспоненты и битам мантиссы в правильном порядке, вы также можете использовать его для сортировки чисел с плавающей запятой. И теоретически вы можете использовать его для строк, только тогда k почти всегда будет больше, чем log N. - person Niki; 10.11.2010
comment
@Steve, @Mark, @nikie: учли ваши комментарии. Спасибо. - person Alexandre C.; 10.11.2010
comment
Технически быструю сортировку невозможно выполнить на месте - требуется O (log n) дополнительного места для записи положения каждой точки поворота. (Обычно маскируется, потому что хранится в локальной переменной и используется рекурсия.) - person j_random_hacker; 10.11.2010
comment
@j_random_hacker: Технически для хранения индекса в массиве длиной N требуется журнал (N) бит, поэтому я не думаю, что какой-либо алгоритм сортировки может быть реализован без дополнительного места ;-) - person Niki; 10.11.2010
comment
@nikie: если вы подсчитываете биты вместо слов log (n) -битов, тогда да, для хранения индекса массива потребуются биты log (n), но исходный ввод теперь имеет размер n * log (n) вместо n , а для быстрой сортировки теперь требуется O (log (n) ^ 2) дополнительных бит пространства - в то время как, например, Для heapsort требуется только O (log n) дополнительных бит. (Но вы обычно предполагаете модель словарного ОЗУ, в которой машинное слово может содержать n в пространстве O (1) и делить все количества на log (n).) - person j_random_hacker; 11.11.2010
comment
@j_random_hacker: здесь практичность сталкивается с теорией, и оба проигрывают. Если вы предполагаете фиксированный верхний предел размера входного массива (чтобы индекс мог храниться в пространстве O (1)), вы нарушаете теоретическую модель ограничения на бесконечность, поэтому это просто вопрос что вы спасаете. Если вы говорите, что log (n) действительно постоянный, вы можете также сказать, что log ^ 2 (n) действительно постоянный. На практике я написал быструю сортировку (для производства), в которой вместо стека вызовов использовался массив фиксированного размера в стеке для хранения списка дел. 240 байт или что-то еще. - person Steve Jessop; 11.11.2010
comment
@ Стив Джессоп: Я тебя слышу. Я пустился в погоню за дикими гусями в поисках окончательного ответа на этот вопрос, но возникла единственная (мутная, неудовлетворительная) картина: при составлении заявлений о времени / пространстве Big-O люди обычно исходят из словесной модели RAM и что слово находится в наименьший log (n) бит. Это означает, что да, мощность машины неявно предполагается масштабируемой с размером входных данных, что абсурдно, хотя, возможно, менее абсурдно, чем другие способы формулирования проблемы. В любом случае по-прежнему существует разница в множителе log (n) в дополнительном пространстве, необходимом для быстрой и динамической сортировки для достаточно большого n. - person j_random_hacker; 12.11.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)

person Niki    schedule 10.11.2010
comment
Это верно не для всех случаев. k не обязательно должно быть битовым счетчиком, это может быть, например, счетчик байтов - если вы сортируете 4-байтовые целые числа, N должно быть меньше 16, чтобы log N было меньше 4. - person Mark Ransom; 11.11.2010
comment
O (N log N) - это ложь. Нет такого. Это O (k N log N) против O (k N) - если вы мне не верите, спросите себя, как в мире сортировка может быть независимой от размера элемента. - person user541686; 09.04.2015
comment
@Mehrdad: Это похоже на аргумент о семантике. Как я понял, N в O (N log N) - это размер ввода, например в битах. Тогда либо элементы имеют постоянный размер, либо имеется только N / k элементов. - person Niki; 09.04.2015
comment
@nikie: конечно, если вы считаете k постоянным, тогда это нормально, но тогда сортировка по основанию равна O (N), а не O (k N). В любом случае нельзя сравнивать k с log N. - person user541686; 09.04.2015
comment
@ Mehrdad: Я понимаю вашу точку зрения. Спасибо за исправление, я обновил свой ответ. - person Niki; 09.04.2015
comment
Здорово! Удалил мой -1 :) Я действительно делал анализ раньше, это отличное упражнение и становится нетривиальным ... если у вас есть время, я предлагаю вам пройти его, потому что есть кроссовер, который вы действительно можете определить (по крайней мере, если вы пренебрегаете эффектами кеширования), но это не так просто, как k против log N. - person user541686; 09.04.2015

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

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

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

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

person Guy    schedule 18.07.2011

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

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

и в моем тесте radix sort в лучшем случае в 7 раз быстрее, чем quicksort.

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]来保证原有的顺序不变      
    }
}
person zhuwenbin    schedule 28.03.2013
comment
Разве для системы radix-256 не потребуется память в 256 раз больше размера исходного массива? - person huseyin tugrul buyukisik; 16.02.2014
comment
нет, как вы можете видеть в кодах, для этого нужны только bar [256] и s [original.length], это дополнительная 1-кратная память исходного массива - person zhuwenbin; 12.07.2015

Сортировка 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 мс, как в случае std::sort C ++ или qsort C ++, что определенно привело бы к очень очевидным сбоям в частоте кадров. Я даже слышал о людях, реализующих радикальную сортировку с помощью SIMD, хотя понятия не имею, как им это удалось. Я недостаточно умен, чтобы придумать такое решение, хотя даже моя наивная небольшая сортировка по основанию системы счисления работает довольно хорошо по сравнению со стандартными библиотеками.

person Community    schedule 04.01.2018
comment
Примечание. Radix sort - это алгоритм сортировки строк, а не числовых. Ладно, это алгоритм лексикографической сортировки. Radix означает основание (как в базе 10 или базе 8), и он может сортировать все, что имеет цифры и места в предопределенном порядке, и что включает строки, если вы выбираете порядок для символов (например, алфавитный, ASCII, кодовая точка Unicode , что бы ни). Вы даже можете думать об английском словаре как о разновидности английских слов с основанием системы счисления, состоящей из 26 сегментов, если хотите. Я говорю, что это строковая сортировка, потому что с точки зрения компьютерных представлений она ближе к обработке числа как строки цифр. - person LinearZoetrope; 15.10.2020
comment
@LinearZoetrope Вы правы! Плохо мое там за грубость. На самом деле теперь мне любопытно, может ли система счисления кратких строк в лексикографической форме превзойти, скажем, интросорт. Я действительно считаю радикальную сортировку незаменимой, но могу понять, почему многие стандартные библиотеки могут ее пропускать, учитывая требования не только для компаратора. - person ; 22.10.2020

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

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

O (k * n) = "наихудший случай работы"

k * n = n^2 (if k = n)

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

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

person kiltek    schedule 20.10.2012

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

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

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

своего рода вопрос:

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

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

person Ivan Š    schedule 23.01.2015

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

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

person Anthony Poblacion    schedule 19.10.2016

в Integer 32bit Sort он будет выполнять быструю сортировку 7-10 раз, но на элементах 1b потребуется заметная память, например, несколько ГБ. Таким образом, вы можете сначала использовать сортировку Radix или Counter только в том случае, если ваши данные n большие, но исходные значения в данных маленькие, или вы можете использовать сортировку любого огромного целочисленного списка, когда вы можете обменять память на скорость

person Tigran Sargsyan    schedule 20.03.2021