它很稳定,时间复杂度为O(n)。它应该比Quicksort和Mergesort之类的算法要快,但我几乎看不到它的使用。
它很稳定,时间复杂度为O(n)。它应该比Quicksort和Mergesort之类的算法要快,但我几乎看不到它的使用。
Answers:
与基数排序不同,quicksort是通用的,而基数排序仅对固定长度整数键有用。
您还必须了解,O(f(n))的真正含义是按K * f(n)的顺序,其中K是任意常数。对于基数排序,此K恰好很大(至少要排序的整数中的位数),另一方面,快速排序具有所有排序算法中最低的K之一,平均复杂度为n * log(n)。因此,在现实生活中,快速排序通常比基数排序更快。
大多数排序算法都是通用的。有了比较功能,它们就可以处理任何事情,并且像Quicksort和Heapsort这样的算法将使用O(1)额外的内存进行排序。
基数排序更专业。您需要按字典顺序的特定键。您需要为每个键中的每个符号使用一个存储桶,并且这些存储桶需要保存很多记录。(或者,您需要一个大型的存储桶数组,用于存储每个可能的键值。)您可能需要更多的内存来进行基数排序,并且您将随机使用它。这两种方法都不适用于现代计算机,因为您可能会遇到诸如Quicksort之类的页面错误,从而导致缓存未命中。
最后,人们通常不再编写自己的排序算法。大多数语言都有可排序的库功能,通常正确的做法是使用它们。由于基数排序并非普遍适用,通常必须针对实际用途进行定制,并且会使用大量额外的内存,因此很难将其放入库函数或模板中。
O(n^2)
由于n
左分区和右分区上的递归调用,在最坏的情况下,快速排序需要内存。如果实现使用尾部递归优化,则可以将其降低到仅O(n)
因为对正确分区的调用不需要额外的空间。(en.wikipedia.org/wiki/Quicksort#Space_complexity)
S(n) \in O(n)
用于使用基数进行排序的空间,即与用于堆或快速排序的空间相同。
n^2
不再提及O(log n)
排序的键实际上是已知的稀疏范围内的整数是非常罕见的。通常,您有字母字段,看起来它们将支持非比较排序,但是由于现实世界中的字符串在整个字母中分布不均匀,因此在理论上效果不佳。
在其他情况下,仅在操作上定义标准(给定两个记录,您可以决定哪个先出现,但是您无法评估独立记录的规模有多“小”)。因此,该方法通常不适用,不如您想象的适用,或者仅比O(n * log(n))快。
我一直在使用它,实际上比基于比较的排序更多,但是我承认我是一个奇怪的人,对数字的使用比其他任何事情都多(我几乎从未使用过字符串,如果是这样的话,它们通常会被内插排序再次可用于过滤重复项并计算集合的相交;我几乎从不进行字典比较。
一个基本示例是作为搜索或中位数拆分的一部分的给定维数的基数排序点,或者是一种检测重合点的快速方法,深度排序片段或对多个循环中使用的索引数组进行基数排序以提供对缓存更友好的访问模式(不要在内存中来回移动,而只是要再次返回并将同一内存重新加载到高速缓存行中)。至少在我的领域(计算机图形学)中,有一个非常广泛的应用程序,用于对固定大小的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
。
另一个原因:目前,排序通常是通过将用户提供的排序例程附加到编译器提供的排序逻辑来实现的。对于基数排序,这将相当复杂,并且当排序例程对可变长度的多个键进行操作时,情况甚至更糟。(说,名字和生日)。
在现实世界中,我实际上已经实现了一次基数排序。这是在内存有限的旧时代,我无法一次将所有数据带入内存。这意味着对数据的访问次数比O(n)vs O(n log n)重要得多。我对数据进行了一次遍历,将每个记录分配到一个bin(通过哪些记录位于哪个bin中的列表,实际上没有移动任何东西。)对于每个非空bin(我的排序键是文本,将会有很多空箱)我检查了是否确实可以将数据带入内存-如果可以,则将其带入并使用quicksort。如果否,则构建一个仅包含垃圾箱中项目的临时文件,然后递归调用例程。(实际上,很少有垃圾箱溢出。)这导致对网络存储进行两次完全读取和一次完全写入,并且对本地存储进行了大约10%的写入。
如今,这样的大数据问题变得更加难以解决,我可能永远也不会再写这样的东西了。(如果这些天我面临着相同的数据,我只需要指定64位操作系统,如果在该编辑器中遇到问题就可以添加RAM。)
如果所有参数都是整数,并且输入参数超过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