【问题标题】:Quicksort not getting quicker快速排序没有变得更快
【发布时间】:2014-03-24 21:33:14
【问题描述】:

我最近了解到人们如何努力使快速排序更快。从随机选择枢轴元素到切换到较小数组的插入排序,甚至使用 3 路分区处理相等的键。我很好奇随机生成的数据是如何工作的,并想分析一些 python 代码。我附上下面的脚本。问题是脚本最终花费了相同的时间!当我使用 %prun 时,看起来快速排序被调用的次数也非常相似。所以,我们所做的所有改进只有在我们的数据遇到最坏的情况时才有用(非常错误地排序?)

def hoare_partition(a, lo, hi):

    if lo >= hi or (lo + 1) == len(a) - 1:
        return None
    pivot = a[lo]
    left = lo + 1
    right = hi


    while left <= right and right < len(a):
        while left < len(a) and a[left] < pivot:
            left += 1
        while a[right] > pivot:
            right -= 1
        if left <= right and right < len(a):
            a[left], a[right] = a[right], a[left]
            left += 1
            right -= 1
    a[lo], a[right] = a[right], a[lo]
    return right

def hoare_quicksort(a, lo, hi):
    ''' this is a vanilla implementation of quick sort. this will call the partition method that uses first element as pivot '''

    if lo < hi:
        p = hoare_partition(a, lo, hi)
        if p:
            #print 'calling for ', lo, p - 1
            hoare_quicksort(a, lo, p - 1)  

            #print 'calling for ', p + 1, hi
            hoare_quicksort(a, p + 1, hi)

这是我们选择第一个元素本身作为枢轴的普通实现。然后,我改为选择中点。

所以,换了一行

mid = lo + (hi - lo)//2

a[lo], a[mid] = a[mid], a[lo]
pivot = a[lo]

然后我也做随机枢轴选择,像这样:

pos = random.randint(lo, hi + 1)


a[lo], a[pos] = a[pos], a[lo]
pivot = a[lo]

现在,我用

来称呼他们
%prun hoare_quicksort([random.randint(0, 10000) for i in xrange(1000)], 0, 999)
%prun mid_quicksort([random.randint(0, 10000) for i in xrange(1000)], 0, 999)
%prun random_quicksort([random.randint(0, 10000) for i in xrange(1000)], 0, 999)

所有这些都花费几乎相同的时间(5.22、5.27、5.61 毫秒)。当我使用 %prun 调用它们并查看调用快速排序的次数时,我再次得到非常相似的数字。那么,怎么了?

【问题讨论】:

标签: python sorting quicksort


【解决方案1】:

你的基准被打破了。

  1. 您正在对 random.randint 的 1000 次迭代进行基准测试,而不是您的类型。
  2. 每个排序只运行一次,因此您需要对操作系统中的线程和进程切换延迟进行基准测试。

尝试预先创建源数组并运行每个排序,甚至数百万次。

【讨论】:

    【解决方案2】:

    因此,我们所做的所有改进只有在我们的数据满足时才有用 最坏的情况(非常错误的方向排序?)

    不一定是最坏的情况,但是数据中任何类型的预先存在的顺序都会对运行时造成不利影响。预先存在的顺序很常见,我们想要一种利用它来运行得更快的排序,而不是看着它就吐出来的排序。

    您已经在随机数据上测试了快速排序。这几乎是快速排序的最佳情况。如果数据来自字典的键,并且使用的哈希导致它们以大部分排序的顺序出现怎么办?

    >>> data = dict.fromkeys(random.sample(xrange(10000), 9000)).keys()
    >>> timeit.timeit('rand_quicksort(data[:], 0, len(data)-1)', 'from __main__ impo
    rt rand_quicksort, data', number=1)
    0.06688880239187256
    >>> timeit.timeit('hoare_quicksort(data[:], 0, len(data)-1)', 'from __main__ imp
    ort hoare_quicksort, data', number=1)
      # about 1000 lines omitted
      File "<stdin>", line 9, in hoare_quicksort
      File "<stdin>", line 9, in hoare_quicksort
      File "<stdin>", line 9, in hoare_quicksort
      File "<stdin>", line 9, in hoare_quicksort
      File "<stdin>", line 9, in hoare_quicksort
      File "<stdin>", line 9, in hoare_quicksort
      File "<stdin>", line 9, in hoare_quicksort
      File "<stdin>", line 9, in hoare_quicksort
      File "<stdin>", line 9, in hoare_quicksort
      File "<stdin>", line 4, in hoare_quicksort
    RuntimeError: maximum recursion depth exceeded
    

    好吧,我们遇到了堆栈溢出,这太可怕了。即使我们不这样做,也需要永远吓坏。

    (如果您想重现此结果,请注意您的代码中有一些错误。if p 应该是 if p is not Nonerandom.randint(lo, hi + 1) 应该是 random.randint(lo, hi)random.randrange(lo, hi + 1)。我必须修复以获得正确的测试结果。)

    【讨论】:

    • 我曾经做“如果 p 不是无”,但现在我做“如果 p”,因为这意味着同样的事情,不是吗?另外,它对我有用(没有 randint)。
    • @crazyaboutliv: 如果p 恰好为零,除非您使用if p is not None,否则递归将提前中止。
    • 哦,是的。我想知道为什么我所有的数组作为输入(用于测试)总是被排序。我会改变的,谢谢。
    • 好吧,在我复制了数据以便修复基准之后。我意识到 random.randint() 调用意味着随机枢轴选择实际上需要更长的时间!这在实践中观察到了吗?
    • @crazyaboutliv:是的。不过,它的影响各不相同。它在真正的随机快速排序实现中不太重要,它会针对小输入大小切换到不同的算法。
    【解决方案3】:

    随机化枢轴选择不会使快速排序变得更快:它有助于避免我们的算法执行最坏的情况。假设我们对一个已经排序的向量进行排序,我们决定选择枢轴作为每个子数组的最右边的元素:这包含这个子数组的最大值,所以快速排序以最不平衡的方式将子数组分成两部分。这可以通过随机化来防止。如果我们确定要避免最坏情况,我们可以说该算法需要相似的时间,直到每个递归级别生成近似恒定平衡的分区,因此我们可以证明递归树深度是恒定的

    【讨论】:

      猜你喜欢
      • 2015-03-13
      • 1970-01-01
      • 2017-11-03
      • 2020-08-14
      • 1970-01-01
      • 2016-12-23
      • 1970-01-01
      • 1970-01-01
      • 2014-10-10
      相关资源
      最近更新 更多