为什么我的快速排序在python中如此缓慢?

为什么我的快速排序在python中如此缓慢?,python,algorithm,sorting,quicksort,Python,Algorithm,Sorting,Quicksort,我试图用python编写一个快速排序(用于学习算法),但我发现它比本机排序慢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.09

我试图用python编写一个快速排序(用于学习算法),但我发现它比本机排序慢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]
def快速排序(lst):
快速排序(lst、0、len(lst)-1)
def quicksortinner(第一、开始、结束):
如果开始>=结束:
返回
j=分区(lst、开始、结束)
quicksortinner(第一级,启动,j-1)
quicksortinner(lst,j+1,结束)
def分区(lst、开始、结束):
pivotindex=random.randrange(开始、结束+1)
交换(lst、数据透视索引、结束)
枢轴=lst[结束]
i、 j=开始,结束-1
尽管如此:
当lst[i]=开始时:
j-=1
如果i>=j:
打破
交换(第一、第一、第三、第三)
交换(第一阶段,第一阶段,结束)
返回i
def交换(lst、a、b):
如果a==b:
返回
lst[a],lst[b]=lst[b],lst[a]

在分区中,我向右扫描,j向左扫描(算法的方法)。早些时候,我尝试了两种方法都向右移动(可能更常见),但差别不大。

本机排序是用C编写的。您的快速排序是用纯Python编写的。预计速度差为10倍。如果使用运行代码,您应该更接近本机速度(PyPy使用跟踪JIT来实现高性能)。类似地,它也会大大提高速度(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步骤,所做的工作超出了您的预期。它将是总运行时间的一个重要组成部分。因为随机的枢轴选择会很慢,所以考虑使用一种选择枢轴的技术。 此外,在CPython中对swap()函数的调用也不快。内联的代码应该给你一个速度提升


正如您所看到的,优化Python不仅仅是选择一个好的算法,还有很多事情要做。希望这个答案能让您进一步实现您的目标:-)

通过移动到,您将获得一个小的加速,尽管这在很大程度上可能是由于本机代码非常快

我以一个例子来说明这一点。抱歉没有使用快速排序-它们的工作速度大致相同,但是MergeSort花费的时间稍微少一些,而且迭代版本更易于演示

从本质上讲,MergeSort通过将字符串分成两半,分别对两个字符串进行排序(当然是使用自身!),并将结果组合在一起来对字符串进行排序-排序后的列表可以在O(n)时间内进行合并,因此这将导致总体O(n log n)性能

下面是一个简单的递归合并排序算法:

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]

因此,我仍然没有接近内置版本,但比以前做得好一点


可以找到迭代快速排序的诀窍。

也许可以在没有任何进一步信息的情况下阅读您的代码,我的钱花在“本机排序速度太快了”上:-)我打赌您是以pythonic的方式实现它的,这使得它很慢,也不是快速排序。你使用列表理解吗?或者你实现了幼稚的版本,你遇到了一个糟糕的情况。你如何选择你的轴心?这会有很大的不同。如果您每次都生成伪随机数,则生成这些数字所花费的时间可能会显著降低算法的速度,降低一个常数系数。@Daniele Edited。ThanksI很生气,用c语言重写了程序。现在,它可以与python原生语言相比,或者更快一点。而在其他语言中,这确实是正确的,根据我的经验,python算法在递归而不是迭代中表现得更好。有趣的是。。。您能给出一些示例代码来支持这一点吗?我想排序有点复杂。
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