浮点数的 Bucket 排序介绍

Bucket 排序是一种基于分布的排序算法,它将数据分割成一个有限的“buckets”数,然后将每个桶的内容逐个排序。当应用到在已知间隔内统一分布的浮点数——通常——桶类型可以实现线性平均时间复杂,使其成为高性能排序任务的强候选物。

核心理念很简单: 与其比较每对元素( 如快速组合或合并组合), 桶排序首先根据它们的值在桶中分配元素。 每个桶自然地将一个狭小的值组合在一起。 之后, 一个简单的排序算法 — 通常插入排序甚至循环调用桶排序 — 完成了工作。 最后, 桶被调整, 以便生成排序的数组 。

文章深入审视了对Python中浮点数的落实桶排序,涵盖了它的力学,复杂性,优点,陷阱,以及现实世界的应用.

如何用小桶排序工作

Bucket 排序假设输入在一个已知范围内统一分布,一般. 算法分三个阶段进行:

  1. 初始化:创建一个n]空桶的数组,其中n是元素的数量.
  2. 分拆 :对于每个元素,计算其桶指数(假设值在 中),并将元素放入桶中.
  3. 吸附和凝聚[:将每个桶分别排序(使用任何稳定或高效的内部排序),然后将桶进行凝聚,以产生最终排序的阵列.

关键的观点是,由于数据分布一致,每个桶平均收到大约n/n=1]元素,这样分类单个桶的成本就非常低——每桶的时间往往不变。

处理边缘案件

当一个浮点数完全等于1.0时,计算出的指数将是,这是没有界限的。一个常见的定律是将指数限制在 上。 实际上,如果你的数据严格 , 这个边框就不会发生,但防守它明智。

在 Python 中执行 Bucket 排序

下文是针对范围的浮点数,对桶进行清洁、生产准备的安装。

def bucket_sort(arr):
 """Sort an array of floats uniformly distributed in [0, 1)."""
 n = len(arr)
 if n <= 1:
 return arr

 # Create empty buckets
 buckets = [[] for _ in range(n)]

 # Distribute elements into buckets
 for num in arr:
 index = int(num * n)
 # Guard against floating-point index = n (e.g., when num == 1.0)
 if index == n:
 index = n - 1
 buckets[index].append(num)

 # Sort each bucket and concatenate
 sorted_arr = []
 for bucket in buckets:
 sorted_arr.extend(sorted(bucket)) # Python's Timsort is efficient

 return sorted_arr

功能使用 Python 的内置 来排序每桶。 对于小桶(通常为0–2个元素),这个功能非常快。 对于生产用途,您可以将 替换为插入排序,以在小桶上更低的顶部。

任意区域分类

如果您的浮点数据跨越以外的范围,则可以在分配前使数值正常化。以下的变异图中,任何的差数范围为:

def bucket_sort_scaled(arr, min_val=None, max_val=None):
 if not arr:
 return arr
 if min_val is None:
 min_val = min(arr)
 if max_val is None:
 max_val = max(arr)

 # Guard against identical values
 if max_val == min_val:
 return arr

 n = len(arr)
 buckets = [[] for _ in range(n)]

 for num in arr:
 # Normalize to [0, 1)
 normalized = (num - min_val) / (max_val - min_val)
 index = int(normalized * n)
 if index == n:
 index = n - 1
 buckets[index].append(num)

 sorted_arr = []
 for bucket in buckets:
 sorted_arr.extend(sorted(bucket))
 return sorted_arr

这个版本比较笼统,但需要了解或计算范围。当数据分布在该范围内大致一致时,它就很有效。

复杂性分析

了解桶类的计算成本对于决定何时使用至关重要。

时间复杂度

  • 最佳案例(统一分布的数据):O(n + k),其中k是桶的数量(通常n]). 分布是O(n),对每桶进行排序平均需要经常时间,因此总体O(n)]。
  • 使用桶插入排序的 普通案: O(n + n2/k]]。用[k = n,此语变为O(n)]
  • 最坏的大小写:当所有元素都落到同一个桶中时,O(n2]]。当数据没有统一分布或相对于元素数量而言,范围很小时,就会发生这种情况。

空间复杂度

Bucket 排序需要 [ [FLT: 0] [n + k] [FLT: 1] 额外的空格, 用于桶及其内装物。 使用 [[FLT: 2] k = n [FLT: 3], 这是 [[FLT: 4] [FLT: 5] 。 所使用的空格可以与合并类型相仿, 高于快速组合等位置类型 。

优点和使用案例

类似贝基特在它所假设的具体情景中闪耀:

  • 统一分布的浮点数据——例如传感器读数,蒙特卡洛模拟输出,或正常化概率.
  • 大型数据集——O(n) 普通案例性能使其对分类数百万个比较种类效率较低的浮标具有吸引力.
  • 外部排序——当数据存放在磁盘上时,桶可以独立处理,并写成分开的文件,然后被调和.
  • Parallel和GPU计算[]——每个桶可以独立排序,允许大规模平行.

一个显著的优点是桶类是稳定(如果每桶类是稳定的),意思是等元素的相对顺序被保留.

限制和考虑

尽管桶型优雅,但有几种限制,使其不适于一般用途的分类:

  • 对输入分布的敏感性:如果数据偏斜(例如,许多值被组合在一起),大多数元素会掉入几个桶,将排序成本提高到O(n2]].
  • 需要先前对范围的了解[:在不知晓最小值和最大值的情况下,无法有效创建桶。上面的缩放版本减轻了这一点,但计算范围会增加一个额外的通过.
  • 记忆上层 :创建n Python列表可以消耗大量内存,特别是对于非常大的数组来说. 链接列表或数组可以减少上层,但Python列表的列表是直截了当的.
  • 每桶分类的顶部:用Python的 来排序许多小桶,会产生可以加起来的函数调用。 对于极小的桶,一个明确的插入类型可能更快。

不使用桶排序时

当数据没有统一分布时, 当范围相对于元素数量而言非常大时, 或者当内存受到极大的限制时, 避免桶排序。 在这种情况下, 比较类, 如 [[FLT: 0]] 快速组合 [[FLT: 1] 或 [[FLT: 2]] 快速组合 [[FLT: 3] 是一个更安全的选择 。

与其他排序算法的比较

Bucket 排序在排序算法中占据了独特的位置。 与常见的选项相比, 情况如下 :

Algorithm Average Time Space Stable Best For
Bucket Sort (with k = n) O(n) O(n) Yes (if per-bucket sort is stable) Uniform floats in known range
Quicksort O(n log n) O(log n) No (typical) General-purpose, in-place
Mergesort O(n log n) O(n) Yes Stable sorting, linked lists
Counting Sort O(n + k) O(k) Yes Integer data with limited range
Radix Sort O(n × w) O(n + 2^w) Yes (LSD) Integers or strings of fixed length

对于浮点数,桶的排序往往比半径(需要比特操纵浮点数)排序要快,并且比数据统一时的O(n log n)[比较类型要快.

实用 Python 提示和优化

选择弹匣数量

设置桶数等于元素数(k = n])是拇指的标准规则,较少的桶增加平均桶数,并降解性能;更多的桶浪费记忆而不提高速度.

小桶使用插入排序

如果想要精细的控件,则用一个自定义的插入方式替换,该类型用于小于,例如,20个元素的桶:

def insertion_sort(arr):
 for i in range(1, len(arr)):
 key = arr[i]
 j = i - 1
 while j >= 0 and arr[j] > key:
 arr[j + 1] = arr[j]
 j -= 1
 arr[j + 1] = key

def bucket_sort_insertion(arr):
 n = len(arr)
 if n <= 1:
 return arr
 buckets = [[] for _ in range(n)]
 for num in arr:
 index = int(num * n)
 if index == n:
 index = n - 1
 buckets[index].append(num)
 sorted_arr = []
 for bucket in buckets:
 insertion_sort(bucket)
 sorted_arr.extend(bucket)
 return sorted_arr

这样做可以减少间接费用,因为Python的]有功能调用间接费用和通用行为,对于0-或1-元素列表来说是过度杀伤.

处理非统一分布

如果您知道数据分布不统一,但仍想使用桶排序,您可以修改桶的边界。例如,如果数据遵循正常分布,您可以创建宽度不均的桶来平衡负载。然而,这需要事先分析数据,而且在实践中很少这样做。

外部资源

进一步阅读时,考虑以下权威参考:

结论

巴克特类是分拣浮点数的优雅高效算法 — — 特别是当数据统一分布和范围已知时。它的线性平均时间复杂性使它成为数据科学家或工程师工具包中有价值的工具。 然而,它对于输入分布和额外内存要求的敏感性意味着它不应该盲目使用。 通过理解何时以及如何应用桶类,并通过在Python中谨慎地应用它,你能够比一般用途的比较类型获得显著的性能收益。

无论你正在排序数百万个传感器测量数据,还是使输出从一个有条理的模拟中正常化,桶类都提供了快速,稳定,并平行的解决方案——只要你的数据按规则运行.