【发布时间】:2013-12-11 01:09:02
【问题描述】:
我对网络上的许多 python 基数排序实现感到非常沮丧。
他们始终使用 10 为基数,并通过除以 10 的幂或取数字的 log10 来获得他们迭代的数字的位数。这是非常低效的,因为与位移相比,log10 并不是一个特别快的操作,位移快了近 100 倍!
一个更有效的实现使用 256 的基数并按字节对数字进行排序。这允许使用非常快速的位运算符来完成所有“字节获取”。不幸的是,似乎绝对没有人在 python 中实现了使用位运算符而不是对数的基数排序。
所以,我把事情掌握在自己的手中,想出了这个野兽,它在小型数组上的运行速度大约是 sorted 的一半,而在大型数组上的运行速度几乎相同(例如len 大约 10,000,000):
import itertools
def radix_sort(unsorted):
"Fast implementation of radix sort for any size num."
maximum, minimum = max(unsorted), min(unsorted)
max_bits = maximum.bit_length()
highest_byte = max_bits // 8 if max_bits % 8 == 0 else (max_bits // 8) + 1
min_bits = minimum.bit_length()
lowest_byte = min_bits // 8 if min_bits % 8 == 0 else (min_bits // 8) + 1
sorted_list = unsorted
for offset in xrange(lowest_byte, highest_byte):
sorted_list = radix_sort_offset(sorted_list, offset)
return sorted_list
def radix_sort_offset(unsorted, offset):
"Helper function for radix sort, sorts each offset."
byte_check = (0xFF << offset*8)
buckets = [[] for _ in xrange(256)]
for num in unsorted:
byte_at_offset = (num & byte_check) >> offset*8
buckets[byte_at_offset].append(num)
return list(itertools.chain.from_iterable(buckets))
这个版本的基数排序的工作原理是找出它必须排序的字节(如果你只传递低于 256 的整数,它只会排序一个字节,等等),然后通过将每个字节从 LSB 向上排序,将它们转储到桶按顺序然后只是将桶链接在一起。对需要排序的每个字节重复此操作,您在 O(n) 时间内就有了漂亮的排序数组。
但是,它的速度并没有想象中那么快,在我将它写成比所有其他基数排序更好的基数排序之前,我想让它更快。
在此运行 cProfile 告诉我很多时间都花在列表的 append 方法上,这让我认为这个块:
for num in unsorted:
byte_at_offset = (num & byte_check) >> offset*8
buckets[byte_at_offset].append(num)
在radix_sort_offset 吃了不少时间。这也是一个块,如果你真的看它,它为整个排序完成了 90% 的工作。这段代码看起来可能是numpy-ized,我认为这会带来相当大的性能提升。不幸的是,我对numpy 的更复杂的功能不是很好,所以无法弄清楚。非常感谢您的帮助。
我目前正在使用itertools.chain.from_iterable 来扁平化buckets,但如果有人有更快的建议,我相信它也会有所帮助。
最初,我有一个 get_byte 函数,它返回一个数字的 nth 字节,但内联代码给了我巨大的速度提升,所以我做到了。
还感谢任何其他有关实现或挤出更多性能的方法的 cmets。我想听听你所拥有的一切。
【问题讨论】:
标签: python sorting optimization numpy radix-sort