【问题标题】:Why quicksort is more popular than radix-sort?为什么快速排序比基数排序更受欢迎?
【发布时间】:2011-04-02 03:24:19
【问题描述】:

为什么快速排序(或内推排序)或任何基于比较的排序算法比基数排序更常见?尤其是对数字进行排序。

基数排序不是基于比较的,因此可能比 O(nlogn) 更快。实际上是 O(kn),其中 k 是用来表示每个项目的位数。并且内存开销并不重要,因为您可以选择要使用的桶的数量,并且所需的内存可能小于合并排序的要求。

它与缓存有关吗?或者可能访问数组中整数的随机字节?

【问题讨论】:

    标签: sorting quicksort radix-sort


    【解决方案1】:

    我想到了两个论点:

    1. Quicksort/Introsort 更灵活:

      Quicksort 和 Introsort 可以很好地处理各种数据。排序所需的只是比较项目的可能性。这对于数字来说是微不足道的,但您也可以对其他数据进行排序。

      另一方面,基数排序只是通过二进制表示对事物进行排序。它从不将项目相互比较。

    2. 基数排序需要更多内存。

      我见过的所有基数排序实现都使用辅助缓冲区来存储部分排序结果。这增加了排序算法的内存需求。如果您只对几 KB 进行排序,这可能不是问题,但如果您进入千兆字节范围,则会产生巨大的差异。

      如果我没记错的话,虽然纸上存在一个就地基数排序算法。

    【讨论】:

    • 第二个参数有一半是错误的。确实,基数排序需要更多内存,但所需的内存取决于您在每次传递中使用的位数(桶数)。因此,例如,所需的内存可能比归并排序的要求要少。
    • 第一个参数是正确的,但我更感兴趣的是数字的默认排序算法是使用快速排序实现的。尤其是库中的实现。基数排序从不将项目相互比较这一事实是一件好事,因为否则它的复杂性将被限制为 O(n*logn)。
    • 可以在 lgN 时间内以恒定空间进行稳定的双向就地分区操作。因此,可以使用 bNlgN 时间在恒定空间中进行就地基数排序,其中“b”是基数的位数。
    【解决方案2】:

    一个明显的答案是,您可以使用快速排序(即任何可比较的类型)对任意类型进行排序,而您只能使用基数对数字进行排序。而且 IMO 快速排序更加直观。

    【讨论】:

    • IMO 冒泡排序比快速排序更直观。
    • @Justin 确实,但速度慢了一点。
    • 没错,但我更感兴趣的是数字的默认排序算法是使用快速排序实现的。尤其是库中的实现,因为直观性并不重要,如果 sort() 函数的实现在后台。
    【解决方案3】:

    对于(大多数)现实世界的用例来说,基数排序比较慢。

    一个原因是算法的复杂性:

    如果项目是唯一的,k >= log(n)。即使有重复项,k

    另一个是实现:

    额外的内存需求(这本身就是一个缺点)会对缓存性能产生负面影响。

    我认为可以肯定地说,许多库(如标准库)使用快速排序,因为它在大多数情况下表现更好。 我不认为“难以实现”或“不太直观”是主要因素。

    【讨论】:

    • 从广义上讲,我想有两个理由担心排序的速度:要么是因为您要对许多小列表进行排序,要么是因为您要对一个巨大的列表进行排序。如果您正在对小的整数列表进行排序,那么假设不会有太多重复项(取决于它们的生成方式)也许是合理的,但是如果您正在对 1000 亿个 32 位整数进行排序,那么必然会有有很多重复。所以一个人的用例很重要。但我同意大多数程序更可能不得不频繁地对小列表进行排序,而不是必须对庞大的列表进行排序。
    • 如果你要对 1000 亿个 32 位整数进行排序,你只需要一个 40 亿个整数数组来存储你看到每个数字的次数的计数器。这没有可比性,是一种比 Radix 更简单的线性算法,其运行步数正好为 1000 亿步。缓存将是一个真正的问题
    【解决方案4】:

    Wikipedia 所述

    与其他排序算法相比,基数排序的效率这个话题有些棘手,并且容易引起很多误解。基数排序是否与最好的基于比较的算法一样有效、效率较低或效率更高,取决于所做假设的细节。对于具有 d 位或更少位数的 n 个键,基数排序效率为 O(d·n)。有时 d 表示为一个常数,这将使基数排序(对于足够大的 n)比最好的基于比较的排序算法更好,这些算法都需要 O(n·log(n)) 次比较。但是,通常不能将 d 视为常数。 特别是,在所有键都是不同的常见(但有时是隐含的)假设下,那么 d 必须至少是 log(n) 的数量级,这充其量(对于密集打包的键)给出了时间复杂度 O (n·log(n))。这似乎使基数排序最多与最好的基于比较的排序一样有效(如果键比 log(n) 长得多,则更糟)。

    反对的论点是基于比较的算法是用比较次数来衡量的,而不是实际的时间复杂度。在某些假设下,比较的平均时间是恒定的,而在其他假设下则不会。随机生成的密钥的比较平均需要恒定的时间,因为在一半情况下密钥在第一位不同,在剩余一半的情况下第二位不同,依此类推,导致平均两位需要比较。在排序算法中,第一次比较满足随机性条件,但随着排序的进行,比较的键显然不再是随机选择的。例如,考虑一个自下而上的归并排序。第一遍将比较随机键对,但最后一遍将比较排序顺序中非常接近的键。

    决定因素是密钥的分配方式。基数排序的最佳情况是它们被视为连续的位模式。这将使键尽可能短,仍然假设它们是不同的。这使得基数排序 O(n·log(n)),但基于比较的排序不会那么有效,因为在此假设下比较不会是恒定时间。如果我们假设密钥是长度为 k·log(n) 的位模式,常数 k > 1 和以 2 为底的 log,并且它们是一致随机的,那么基数排序仍然是 O(n·log(n) ),但基于比较的排序也是如此,因为“额外”长度甚至使排序结果中连续的键也有足够的差异,以至于比较平均是恒定时间。 如果键长于 O(log(n)),但是是随机的,那么基数排序会较差。 还有许多其他假设也可以做出,并且大多数都需要仔细研究才能得出正确的比较。

    【讨论】:

    • 该部分已从维基百科中删除,谈话中的讨论认为其中部分内容不正确。
    【解决方案5】:

    其他答案中的观点是有效的,但就您在几个 cmets 中提到的问题而言

    ...数字的默认排序算法是使用快速排序实现的。尤其是库中的实现...

    快速排序是“安全”的选择。 基于计数排序的基数排序的潜在运行时间非常有吸引力,是的,但是基数排序很容易在恶意/不幸的数据集上表现不佳。如果被排序的键的位数接近被排序的键的数量,则基数排序在 n^2 上执行,并且空间复杂度不可忽略,并且除了数字之外,它往往具有相当高的内置运行时常量正在排序的键的位数。
    Mergesort 之所以有吸引力,是因为它的行为在某些方面类似于在每个机会(中位数)处选择最佳支点的快速排序。但是,它具有可观的空间复杂性。它不像基数那样容易受到恶意/不幸数据的影响,但也不提供有吸引力的可能运行时间。 除了几乎(或完全)排序的数据集之外,基本的快速排序在大多数数据集上都表现得非常好,并且具有很小的空间复杂度。
    通过将快速排序转换为随机快速排序,可以轻松解决快速排序的漏洞。 Radix sort 的漏洞是通过对被排序的键设置限制来解决的,这会固有地限制库的用户。快速排序在小型数据集上比合并性能更高,并且在合并可能更快时执行合理。
    在实现一个库时,您希望它具有通用性。以这些示例为例,一个 Web 应用程序和一个带有极其受限的微控制器的小型设备。 Web应用程序需要定期处理恶意数据,也有各种各样的需求。具有预先限制的库不太可能有用。在微控制器的情况下,它可能在空间上受到限制,并且无法放弃可以节省的一点点。快速排序可以节省空间,并且如果出现速度较慢的情况,则只会以常数乘数较慢地完成。
    总之 -
    1.) 库通常被编码为尽可能多的通用可用性
    2.) 良好的性能是可以接受的,尤其是在很多情况下,最好的性能
    3.) 空间并不总是一个主要问题,但当它是主要问题时,它通常是明确限制性的,所以

    【讨论】:

      【解决方案6】:

      基数排序的效率 = O(c.n) 其中 c = 输入键集中的最高位数。 n = 输入键集中的键数。

      快速排序的最佳情况 = O(n. log n) 其中 n = 输入键集中的键数。

      假设 16 个数字按 6 位排序:

      基数排序 = 16 * 6 = 96 个时间单位。 快速排序 = 16 * 4 = 64 个时间单位。

      课程: 当 'c' 较小时,Radix 确实赢了。当它很高时,它会失败。快速排序与键中的位数无关,这使它更好,更实用

      【讨论】:

      • Quicksort 需要 O(n log n) comparisons(同样重要的是这是平均情况,而不是最坏情况)。这很重要,因为这意味着快速排序不是“与键中的位数无关”。这意味着您正在将苹果与橙子进行比较。如果你想比较喜欢它意味着你必须考虑执行比较功能的成本。对于字长整数,它是常数,但不是一般情况。
      • 建议将 Radix 的获胜条件改为“当 'c' 小于或 'n' 很大时”;在 c
      • 时间复杂度上限的要点是确保程序在n 相当大的合理时间内完成。我们并不真正关心 96/64 时间单位的情况。
      猜你喜欢
      • 2018-03-28
      • 2011-11-30
      • 2018-05-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-18
      • 2021-07-27
      相关资源
      最近更新 更多