【发布时间】:2011-11-26 15:53:36
【问题描述】:
我使用 SciPy 中的数组重写了 Wikipedia 中用于 Python 的原始基数排序算法,以提高性能并减少代码长度,我设法做到了。然后我采用了 Literate Programming 中的 classic(内存中,基于枢轴)快速排序算法并比较了它们的性能。
我曾期望基数排序会在超过某个阈值时击败快速排序,但事实并非如此。此外,我发现Erik Gorset's Blog's 提出了一个问题“基数排序比整数数组的快速排序更快吗?”。答案是这样的
.. 基准测试显示 MSB 就地基数排序始终比大型数组的快速排序快 3 倍以上。
很遗憾,我无法重现结果;不同之处在于 (a) Erik 选择了 Java 而不是 Python,并且 (b) 他使用 MSB 就地基数排序,而我只是在 Python 字典中填充 buckets .
根据理论基数排序应该比快速排序更快(线性);但显然这在很大程度上取决于实施。那么我的错误在哪里?
这是比较两种算法的代码:
from sys import argv
from time import clock
from pylab import array, vectorize
from pylab import absolute, log10, randint
from pylab import semilogy, grid, legend, title, show
###############################################################################
# radix sort
###############################################################################
def splitmerge0 (ls, digit): ## python (pure!)
seq = map (lambda n: ((n // 10 ** digit) % 10, n), ls)
buf = {0:[], 1:[], 2:[], 3:[], 4:[], 5:[], 6:[], 7:[], 8:[], 9:[]}
return reduce (lambda acc, key: acc.extend(buf[key]) or acc,
reduce (lambda _, (d,n): buf[d].append (n) or buf, seq, buf), [])
def splitmergeX (ls, digit): ## python & numpy
seq = array (vectorize (lambda n: ((n // 10 ** digit) % 10, n)) (ls)).T
buf = {0:[], 1:[], 2:[], 3:[], 4:[], 5:[], 6:[], 7:[], 8:[], 9:[]}
return array (reduce (lambda acc, key: acc.extend(buf[key]) or acc,
reduce (lambda _, (d,n): buf[d].append (n) or buf, seq, buf), []))
def radixsort (ls, fn = splitmergeX):
return reduce (fn, xrange (int (log10 (absolute (ls).max ()) + 1)), ls)
###############################################################################
# quick sort
###############################################################################
def partition (ls, start, end, pivot_index):
lower = start
upper = end - 1
pivot = ls[pivot_index]
ls[pivot_index] = ls[end]
while True:
while lower <= upper and ls[lower] < pivot: lower += 1
while lower <= upper and ls[upper] >= pivot: upper -= 1
if lower > upper: break
ls[lower], ls[upper] = ls[upper], ls[lower]
ls[end] = ls[lower]
ls[lower] = pivot
return lower
def qsort_range (ls, start, end):
if end - start + 1 < 32:
insertion_sort(ls, start, end)
else:
pivot_index = partition (ls, start, end, randint (start, end))
qsort_range (ls, start, pivot_index - 1)
qsort_range (ls, pivot_index + 1, end)
return ls
def insertion_sort (ls, start, end):
for idx in xrange (start, end + 1):
el = ls[idx]
for jdx in reversed (xrange(0, idx)):
if ls[jdx] <= el:
ls[jdx + 1] = el
break
ls[jdx + 1] = ls[jdx]
else:
ls[0] = el
return ls
def quicksort (ls):
return qsort_range (ls, 0, len (ls) - 1)
###############################################################################
if __name__ == "__main__":
###############################################################################
lower = int (argv [1]) ## requires: >= 2
upper = int (argv [2]) ## requires: >= 2
color = dict (enumerate (3*['r','g','b','c','m','k']))
rslbl = "radix sort"
qslbl = "quick sort"
for value in xrange (lower, upper):
#######################################################################
ls = randint (1, value, size=value)
t0 = clock ()
rs = radixsort (ls)
dt = clock () - t0
print "%06d -- t0:%0.6e, dt:%0.6e" % (value, t0, dt)
semilogy (value, dt, '%s.' % color[int (log10 (value))], label=rslbl)
#######################################################################
ls = randint (1, value, size=value)
t0 = clock ()
rs = quicksort (ls)
dt = clock () - t0
print "%06d -- t0:%0.6e, dt:%0.6e" % (value, t0, dt)
semilogy (value, dt, '%sx' % color[int (log10 (value))], label=qslbl)
grid ()
legend ((rslbl,qslbl), numpoints=3, shadow=True, prop={'size':'small'})
title ('radix & quick sort: #(integer) vs duration [s]')
show ()
###############################################################################
###############################################################################
这是比较大小在 2 到 1250(横轴)范围内的整数数组的排序持续时间(以秒为单位)(对数纵轴)的结果;下面的曲线属于快速排序:
快速排序在功率变化时很平滑(例如,在 10、100 或 1000 时),但基数排序只是跳跃一点,但在质量上遵循与快速排序相同的路径,只是慢得多!
【问题讨论】:
-
包含 1250 个元素的数组并不是真正的大数组。对 1000000 个元素进行排序会得到什么结果?
-
当你抛出一个包含 1,000,000 或 10,000,000 个值的列表时会发生什么?看起来你有一个非常低效的基数排序实现(比如在内部循环中计算 10**digit 和许多不必要的函数调用),所以当你只有 1250 个元素时,理论上的效率可能不可见进行排序。
-
顺便说一句,您的代码很难阅读,但在我看来,您似乎依赖于迭代 dict 以数字顺序为您提供键。这意味着您依赖于未定义的行为,因此您的代码可能随时中断。
-
@Duncan:嗯,我已经预先计算数字的能力,并且正在使用查找来提高性能,但这并没有帮助;我没有发现任何显着的改进。 不必要的函数调用是什么意思? lambda 表达式?
-
@sth:好吧,问题是实现显然太慢了,以至于我可以设法对多达 10000 个列表进行排序,之后非常慢。尽管算法之间的相对距离似乎缩小了,但即使是 10000 快速排序也更快。
标签: python quicksort radix-sort