Я переписал исходный алгоритм сортировки по основанию для Python из Wikipedia, используя массивы из SciPy, чтобы повысить производительность и уменьшить длину кода, что мне удалось. Затем я взял классический (в памяти, на основе сводных данных) алгоритм быстрой сортировки из Literate Programming и сравнил их производительность.
Я ожидал, что радиксная сортировка превзойдет быструю сортировку за пределами определенного порога, но этого не произошло. Кроме того, я нашел блог Эрика Горсета задавая вопрос "Быстрее ли сортировка по основанию для целочисленных массивов, чем быстрая сортировка?". Ответ такой:
... тест показывает, что сортировка по основанию MSB на месте более чем в 3 раза быстрее, чем быстрая сортировка для больших массивов.
К сожалению, воспроизвести результат не удалось; разница в том, что (а) Эрик выбрал Java, а не Python, и (б) он использует сортировку по основанию счисления MSB, тогда как я просто заполняю корзины внутри словаря Python .
Согласно теории, поразрядная сортировка должна быть более быстрой (линейной) по сравнению с быстрой сортировкой; но, видимо, это во многом зависит от реализации. Так в чем же моя ошибка?
Вот код, в котором сравниваются оба алгоритма:
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), но сортировка по основанию только немного прыгает, но в остальном качественно следует по тому же пути, что и быстрая сортировка, только намного медленнее!