为什么不经常使用Radix Sort?


31

它很稳定,时间复杂度为O(n)。它应该比Quicksort和Mergesort之类的算法要快,但我几乎看不到它的使用。


2
参见此处:en.wikipedia.org/wiki/Radix_sort#Efficiency效率为O(kn),可能不比O(n * log(n))好。
FrustratedWithFormsDesigner

2
基数排序通常用于游戏等软实时系统中。像往常一样,一种算法是否优于另一种算法取决于问题的所有参数,而不仅取决于复杂性边界
awdz9nld

@FrustratedWithFormsDesigner也许维基已经改变了?我不再看到对`n log(n)的引用,FWIW ...
rogerdpack

Boost有一个(原位变体):boost.org/doc/libs/1_62_0/libs/sort/doc/html/sort/sort_hpp.html但是,是的,我想人们只是不知道它的存在...要么要么只是使用“标准”排序算法,无论出于何种原因,框架创建者仍倾向于重用效率不高的“通用”排序...也许他们不专注于对整数进行排序通常,因为这是一种罕见的用例?
rogerdpack

Answers:


38

与基数排序不同,quicksort是通用的,而基数排序仅对固定长度整数键有用。

您还必须了解,O(f(n))的真正含义是按K * f(n)的顺序,其中K是任意常数。对于基数排序,此K恰好很大(至少要排序的整数中的位数),另一方面,快速排序具有所有排序算法中最低的K之一,平均复杂度为n * log(n)。因此,在现实生活中,快速排序通常比基数排序更快。


请注意有关复杂度的说明:尽管(LSD)基数排序的复杂度为O(n * K),但此常数通常很小,通常选择为(2 ^(W / K))* C符合L1,其中C是计数器的字节大小,W是要排序的键的大小。大多数实现在x86上为32位字选择K = [3,4]。当每个基数被单独排序时,还可以使K自适应以利用时间相干性(近距离排序)。
awdz9nld

11
关于通用性的说明:Radix sort完全能够对浮点键和可变长度整数键进行操作
awdz9nld 2014年

20

大多数排序算法都是通用的。有了比较功能,它们就可以处理任何事情,并且像Quicksort和Heapsort这样的算法将使用O(1)额外的内存进行排序。

基数排序更专业。您需要按字典顺序的特定键。您需要为每个键中的每个符号使用一个存储桶,并且这些存储桶需要保存很多记录。(或者,您需要一个大型的存储桶数组,用于存储每个可能的键值。)您可能需要更多的内存来进行基数排序,并且您将随机使用它。这两种方法都不适用于现代计算机,因为您可能会遇到诸如Quicksort之类的页面错误,从而导致缓存未命中。

最后,人们通常不再编写自己的排序算法。大多数语言都有可排序的库功能,通常正确的做法是使用它们。由于基数排序并非普遍适用,通常必须针对实际用途进行定制,并且会使用大量额外的内存,因此很难将其放入库函数或模板中。


实际上,O(n^2)由于n左分区和右分区上的递归调用,在最坏的情况下,快速排序需要内存。如果实现使用尾部递归优化,则可以将其降低到仅O(n)因为对正确分区的调用不需要额外的空间。(en.wikipedia.org/wiki/Quicksort#Space_complexity
混沌的分裂

您只需要S(n) \in O(n)用于使用基数进行排序的空间,即与用于堆或快速排序的空间相同。
Velda

@SplinterofChaos Wiki是否已更改?它似乎n^2不再提及O(log n)
quicksort

我认为这不是“很多”更多的内存,也许是2 * n(可以,但不是不可能)吗?而且存储桶是如此之小(假设您要分割字节并递归)以至于它很适合缓存?
rogerdpack

5

排序的键实际上是已知的稀疏范围内的整数是非常罕见的。通常,您有字母字段,看起来它们将支持非比较排序,但是由于现实世界中的字符串在整个字母中分布不均匀,因此在理论上效果不佳。

在其他情况下,仅在操作上定义标准(给定两个记录,您可以决定哪个先出现,但是您无法评估独立记录的规模有多“小”)。因此,该方法通常不适用,不如您想象的适用,或者仅比O(n * log(n))快。


Radix sort可以通过“一次一个字节”地对它们进行递归排序来处理任何范围内的整数(或字符串),因此它们不必处于稀疏范围内FWIW ...
rogerdpack

4

我一直在使用它,实际上比基于比较的排序更多,但是我承认我是一个奇怪的人,对数字的使用比其他任何事情都多(我几乎从未使用过字符串,如果是这样的话,它们通常会被内插排序再次可用于过滤重复项并计算集合的相交;我几乎从不进行字典比较。

一个基本示例是作为搜索或中位数拆分的一部分的给定维数的基数排序点,或者是一种检测重合点的快速方法,深度排序片段或对多个循环中使用的索引数组进行基数排序以提供对缓存更友好的访问模式(不要在内存中来回移动,而只是要再次返回并将同一内存重新加载到高速缓存行中)。至少在我的领域(计算机图形学)中,有一个非常广泛的应用程序,用于对固定大小的32位和64位数字键进行排序。

我想说的一件事是,基数排序可以对浮点数和负数起作用,尽管很难编写出尽可能可移植的FP版本。同样,当它是O(n * K)时,K只是密钥大小的字节数即可(例如:如果存储桶中有2 ^ 8个条目,则一百万个32位整数通常需要4字节大小的通过)。内存访问模式也比快速排序更易于缓存,尽管它通常需要并行数组和小存储桶数组(第二个通常可以很好地放在堆栈上)。QS可能进行五千万次交换,以使用零星的随机访问模式对一百万个整数进行排序。基数排序可以通过对数据进行4次线性,对缓存友好的传递来实现。

但是,缺乏以小数点K加上负数和浮点数来执行此操作的意识,很可能会导致缺乏基数类型的普及。

至于我为什么人们不经常使用它的观点,它可能与许多域无关,这些域通常不需要对数字进行排序或将其用作搜索关键字。但是,仅根据我的个人经验,我的许多前同事也没有在完全合适的情况下使用它,部分原因是他们不知道可以将其用于FP和底片。所以,除了只工作数字类型,它通常被认为是甚普遍适用的比实际的。如果我认为它不适用于浮点数和负整数,也不会有太多用处。

一些基准:

Sorting 10000000 elements 3 times...

mt_sort_int: {0.135 secs}
-- small result: [ 12 17 17 22 52 55 67 73 75 87 ]

mt_radix_sort: {0.228 secs}
-- small result: [ 12 17 17 22 52 55 67 73 75 87 ]

std::sort: {1.697 secs}
-- small result: [ 12 17 17 22 52 55 67 73 75 87 ]

qsort: {2.610 secs}
-- small result: [ 12 17 17 22 52 55 67 73 75 87 ]

那只是我的幼稚实现(mt_sort_int也是基数排序,但考虑到可以假定键是整数,因此具有更快的代码分支)。想象一下由专家编写的标准实现有多快。

我发现基数排序的结果比C ++真正基于快速比较的情况差的唯一情况std::sort是针对非常少的元素(例如32),在这一点上,我认为std::sort开始使用的排序更适合于最小数量的元素,例如堆排序或插入排序,尽管那时我的实现只使用std::sort


1
总是很高兴听到有经验的人的意见。
弗兰克·希勒曼


1

另一个原因:目前,排序通常是通过将用户提供的排序例程附加到编译器提供的排序逻辑来实现的。对于基数排序,这将相当复杂,并且当排序例程对可变长度的多个键进行操作时,情况甚至更糟。(说,名字和生日)。

在现实世界中,我实际上已经实现了一次基数排序。这是在内存有限的旧时代,我无法一次将所有数据带入内存。这意味着对数据的访问次数比O(n)vs O(n log n)重要得多。我对数据进行了一次遍历,将每个记录分配到一个bin(通过哪些记录位于哪个bin中的列表,实际上没有移动任何东西。)对于每个非空bin(我的排序键是文本,将会有很多空箱)我检查了是否确实可以将数据带入内存-如果可以,则将其带入并使用quicksort。如果否,则构建一个仅包含垃圾箱中项目的临时文件,然后递归调用例程。(实际上,很少有垃圾箱溢出。)这导致对网络存储进行两次完全读取和一次完全写入,并且对本地存储进行了大约10%的写入。

如今,这样的大数据问题变得更加难以解决,我可能永远也不会再写这样的东西了。(如果这些天我面临着相同的数据,我只需要指定64位操作系统,如果在该编辑器中遇到问题就可以添加RAM。)


有时提到的对基数排序的缺点之一令人着迷,这是“占用更多空间”。仍在努力解决这个问题……
rogerdpack

1
@rogerdpack不是我的方法使用了更少的空间,而是它使用了更少的数据访问权限。我正在处理一个大约1 GB的文件,同时处理了总内存使用量不到16mb的编译器限制(这是DOS保护模式,不是Windows),包括代码和64kb的结构限制。
劳伦·佩希特尔

-1

如果所有参数都是整数,并且输入参数超过1024个,则基数排序总是更快。

为什么?

Complexity of radix sort = max number of digits x number of input parameters.

Complexity of quick sort = log(number of input parameters) x   number of input parameters

所以基数排序更快

log(n)> max num of digits

Java中的最大整数是2147483647。这是10位长

所以基数排序总是更快

log(n)> 10

因此,基数排序总是更快 n>1024


在实现细节中有隐藏的常量,但是基本上您是说“对于更大的输入基数排序更快”,这应该是事实!很难找到用例,但是在可以的时候……
rogerdpack
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.