我试着在python中编写一个quicksort(用于学习算法),但我发现它比本机排序慢了10倍。这就是结果:
16384 numbers:
native: 5.556 ms
quicksort: 96.412 ms
65536 numbers:
native: 27.190 ms
quicksort: 436.110 ms
262144 numbers:
native: 151.820 ms
quicksort: 1975.943 ms
1048576 numbers:
native: 792.091 ms
quicksort: 9097.085 ms
4194304 numbers:
native: 3979.032 ms
quicksort: 39106.887 ms
这是否意味着我的实施有问题? 或者那没关系,因为本机排序使用了大量的低级优化?
然而,尽管我只是为了学习而不是实际应用而编写它,但我觉得将100万个数字排序接近10个是不可接受的。我的电脑很快。 这是我的代码:
def quicksort(lst):
quicksortinner(lst,0,len(lst)-1)
def quicksortinner(lst,start,end):
if start>=end:
return
j=partition(lst,start,end)
quicksortinner(lst,start,j-1)
quicksortinner(lst,j+1,end)
def partition(lst,start,end):
pivotindex=random.randrange(start,end+1)
swap(lst,pivotindex,end)
pivot=lst[end]
i,j=start,end-1
while True:
while lst[i]<=pivot and i<=end-1:
i+=1
while lst[j]>=pivot and j>=start:
j-=1
if i>=j:
break
swap(lst,i,j)
swap(lst,i,end)
return i
def swap(lst,a,b):
if a==b:
return
lst[a],lst[b]=lst[b],lst[a]
在分区中,我向右扫描并向左扫描j(来自算法的方法)。早些时候我尝试过两种方式向右移动(可能更常见),并没有太大区别。
答案 0 :(得分:6)
本机排序用C语言编写。您的快速排序是用纯Python编写的。预计速度差为10倍。如果使用PyPy运行代码,则应该更接近本机速度(PyPy使用跟踪JIT来实现高性能)。同样,Cython也会提高速度(Cython是Python-to-C编译器)。
一种判断算法是否在同一个球场中的方法是计算两种排序算法使用的比较次数。在精细调整的代码中,比较成本主导了运行时间。这是一个计算比较的工具:
class CountCmps(float):
def __lt__(self, other):
global cnt
cnt += 1
return float.__lt__(self, other)
>>> from random import random
>>> data = [CountCmps(random()) for i in range(10000)]
>>> cnt = 0
>>> data.sort()
>>> cnt
119883
另一个因素是你对 random.randrange()的调用有许多纯Python步骤,并且做的工作比你预期的要多。它将是总运行时间的一个非常重要的组成部分。由于随机枢轴选择可能很慢,请考虑使用median-of-three技术来选择枢轴。
此外,在CPython中调用 swap()函数并不快。内联代码应该可以提高速度。
正如您所看到的,优化Python还有很多,而不仅仅是选择一个好的算法。希望这个答案能让你进一步实现目标: - )
答案 1 :(得分:1)
通过转移到iteration instead of recursion,您将获得一个小的加速,虽然这很大一部分可能是由于本机代码非常快。
我参考MergeSort来说明这一点。不使用QuickSort的道歉 - 它们以大致相同的速度工作,但MergeSort花费的时间少了很多,而且迭代版本更容易演示。
基本上,MergeSort通过将字符串分成两半来对字符串进行排序,分别对它们进行排序(当然是使用它自己!),然后组合结果 - 排序列表可以在O(n)时间内合并,这样就可以得到整体字符串(n log n)表现。
这是一个简单的递归MergeSort算法:
def mergeSort(theList):
if len(theList) == 1:
return theList
theLength = int(len(theList)/2)
return mergeSorted( mergeSort(theList[0:theLength]), mergeSort(theList[theLength:]) )
def mergeSorted(theList1,theList2):
sortedList = []
counter1 = 0
counter2 = 0
while True:
if counter1 == len(theList1):
return sortedList + theList2[counter2:]
if counter2 == len(theList2):
return sortedList + theList1[counter1:]
if theList1[counter1] < theList2[counter2]:
sortedList.append(theList1[counter1])
counter1 += 1
else:
sortedList.append(theList2[counter2])
counter2 += 1
正如您所发现的那样,这可以通过内置排序算法击败地面:
import timeit
setup = """from __main__ import mergeSortList
import random
theList = [random.random() for x in xrange(1000)]"""
timeit.timeit('theSortedList1 = sorted(theList)', setup=setup, number=1000)
#0.33633776246006164
timeit.timeit('theSortedList1 = mergeSort(theList)', setup=setup, number=1000)
#8.415547955717784
但是,通过消除mergeSort
函数中的递归函数调用可以获得一点时间提升(这也避免了达到递归限制的危险)。这是通过从基本元素开始,并成对组合,自下而上的方法而不是自上而下的方法来完成的。例如:
def mergeSortIterative(theList):
theNewList = map(lambda x: [x], theList)
theLength = 1
while theLength < len(theList):
theNewNewList = []
pairs = zip(theNewList[::2], theNewList[1::2])
for pair in pairs:
theNewNewList.append( mergeSorted( pair[0], pair[1] ) )
if len(pairs) * 2 < len(theNewList):
theNewNewList.append(theNewList[-1])
theLength *= 2
theNewList = theNewNewList
return theNewNewList[0]
现在,每次迭代都会存储增长的排序列表元素,从而大大降低了内存需求并消除了递归函数调用。运行它可以在我的运行时间内提供大约15%的速度提升 - 这是一个快速抛出的版本
setup = """from __main__ import mergeSortIterative
import random
theList = [random.random() for x in xrange(1000)]"""
timeit.timeit('theSortedList1 = mergeSortIterative(theList)', setup=setup, number=1000)
#7.1798827493580575
所以我仍然没有 - 在内置版本附近,但比我之前做的好一点。
可以找到迭代QuickSort的配方here。