【问题标题】:What is the worst case complexity for bucket sort?桶排序的最坏情况复杂度是多少?
【发布时间】:2012-03-20 17:42:25
【问题描述】:

我刚刚阅读了有关Bucket sort 的维基百科页面。在这篇文章中,他们说最坏情况的复杂度是 O(n²)。但我认为最坏情况的复杂度是 O(n + k),其中 k 是桶的数量。这就是我计算这种复杂性的方法:

  1. 将元素添加到存储桶。使用链表是 O(1)
  2. 遍历列表并将元素放入正确的桶中 = O(n)
  3. 合并存储桶 = O(k)
  4. O(1) * O(n) + O(k) = O(n + k)

我错过了什么吗?

【问题讨论】:

    标签: algorithm sorting bucket-sort


    【解决方案1】:

    为了合并存储桶,首先需要对它们进行排序。考虑维基百科文章中给出的伪代码:

    function bucketSort(array, n) is
      buckets ← new array of n empty lists
      for i = 0 to (length(array)-1) do
        insert array[i] into buckets[msbits(array[i], k)]
      for i = 0 to n - 1 do
        nextSort(buckets[i])
      return the concatenation of buckets[0], ..., buckets[n-1]
    

    nextSort(buckets[i]) 对每个单独的存储桶进行排序。通常,使用不同的排序来对存储桶进行排序(即插入排序),因为一旦你确定大小,不同的非递归排序通常会给你更好的性能。

    现在,考虑所有n 元素最终都在同一个桶中的情况。如果我们使用插入排序对单个桶进行排序,这可能会导致O(n^2) 的最坏情况性能。我认为答案必须取决于您选择对各个存储桶进行排序的排序。

    【讨论】:

    • 但是如果我们用归并排序对每个桶进行排序,在这种情况下,即使所有元素都添加到同一个桶中,它仍然是 O(nlogn)。你有什么看法?
    【解决方案2】:

    如果算法决定每个元素都属于同一个桶怎么办?在这种情况下,每次添加元素时都需要遍历该桶中的链表。这需要 1 步,然后是 2,然后是 3、4、5... n 。因此,时间是从 1 到 n 的所有数字的总和,即 (n^2 + n)/2,即 O(n^2)。

    当然,这是“最坏情况”(一个桶中的所有元素)——计算哪个桶放置元素的算法通常旨在避免这种行为。

    【讨论】:

    • 不一定,每次都可以添加到列表的最前面,给定O(1)的性能。但是,无论哪种方式,您最终都需要对单个存储桶进行排序,这是(我认为)最坏情况O(n^2) 性能的来源。
    • 我的回答有点简化 - 你不添加到列表的前面是有原因的,我将在编辑中添加
    • 这是我的理解,但我不是 100% 有信心:答案来自这样一个事实,即桶排序是为了改进基于比较的排序的 nlogn 下限。如果添加到列表的前面,则需要在每个存储桶内进行排序——这将我们带回到基于比较的排序的 nlogn 上限/下限。因此,桶排序希望将元素按顺序放入桶中。在一般情况下,这一切都很好。但是,在它试图击败 nlogn 的过程中,这种最坏的情况确实出现了。谁能确认这是真/假?
    • 我很抱歉,但我认为这是错误的。 @smessing 在他的回答 [IMO] 中给出了正确的原因 - 每个桶的递归调用 [或不同的排序] - 如果桶的大小仍然与原始数组相同 [或几乎相同的大小] - 你获得了没有。它类似于快速排序的最坏情况 - 您选择的枢轴始终是最小的元素。
    • 但是,如果我们为每种可用的值创建一个桶会怎样。那么桶中的所有元素都是相等的,我们不需要第二次排序。
    【解决方案3】:

    如果您可以保证每个桶代表一个唯一值(等效项),那么正如您所指出的那样,最坏情况的时间复杂度将是 O(m+n)。

    【讨论】:

      【解决方案4】:

      桶排序假设输入来自均匀分布。这意味着每个存储桶中都有一些项目。反过来,这导致 O(n) 的良好平均运行时间。实际上,如果在每个桶中插入 n 个元素,使得 O(1) 个元素落在每个不同的桶中(插入需要每个项目 O(1)),那么使用插入排序对桶进行排序平均需要 O(1)以及(几乎所有关于算法的教科书都证明了这一点)。由于您必须对 n 个桶进行排序,因此平均复杂度为 O(n)。

      现在,假设输入不是来自均匀分布。正如@mfrankli 已经指出的那样,在最坏的情况下,这可能会导致所有项目都落在例如第一个桶中的情况。在这种情况下,插入排序在最坏的情况下需要 O(n^2)。

      请注意,您可以使用以下技巧来保持相同的平均 O(n) 复杂度,同时在最坏的情况下提供 O(n log n) 复杂度。与其使用插入排序,不如在最坏的情况下简单地使用复杂度为 O(n log n) 的算法:合并排序或堆排序(但不是快速排序,平均仅实现 O(n log n))。

      【讨论】:

        【解决方案5】:

        这是对@perreal 的附加回答。我试图将它作为评论发布,但它太长了。 @perreal 正确地指出了桶排序何时最有意义。不同的答案对正在排序的数据做出不同的假设。例如。如果要排序的键是字符串,那么可能的键的范围将太大(大于桶数组),我们将不得不只使用字符串的第一个字符作为桶位置或其他策略。必须对各个桶进行排序,因为它们保存具有不同键的项目,导致 O(n^2)。

        但是,如果我们对键是已知范围内的整数的数据进行排序,那么桶总是已经排序,因为桶中的键是相等的,这导致了线性时间排序。不仅桶是排序的,而且排序是稳定的,因为我们可以按照添加的顺序从桶数组中拉出项目。

        我想补充的是,如果由于要排序的键的性质而面临 O(n^2),则桶排序可能不是正确的方法。当您有一系列与输入大小成正比的可能键时,您可以通过让每个桶只保存一个键的值来利用线性时间桶排序。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2021-04-11
          • 2015-01-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-01-12
          • 2013-02-21
          相关资源
          最近更新 更多