【问题标题】:Quicksort sorts larger numbers faster?快速排序更快地对较大的数字进行排序?
【发布时间】:2011-02-10 23:40:06
【问题描述】:

我正在用 Python 乱搞,试图练习我的排序算法并发现了一些有趣的东西。

我有三个不同的数据:

  • x = 要排序的数字数
  • y = 数字所在的范围(所有随机生成的整数)
  • z = 排序总时间

时间:
x = 100000 和
y = (0,100000) 然后
z = 0.94182094911 秒

时间:
x = 100000 和
y = (0,100) 然后
z = 12.4218382537 秒

时间:
x = 100000 和
y = (0,10) 然后
z = 110.267447809 秒

有什么想法吗?

代码:

import time
import random
import sys

#-----Function definitions

def quickSort(array): #random pivot location quicksort. uses extra memory.
    smaller = []
    greater = []
    if len(array) <= 1:
        return array
    pivotVal = array[random.randint(0, len(array)-1)]
    array.remove(pivotVal)
    for items in array:
        if items <= pivotVal:
            smaller.append(items)
        else:
            greater.append(items)
    return concat(quickSort(smaller), pivotVal, quickSort(greater))

def concat(before, pivot, after):
    new = []
    for items in before:
        new.append(items)
    new.append(pivot)
    for things in after:
        new.append(things)
    return new

#-----Variable definitions
list = []
iter = 0
sys.setrecursionlimit(20000)
start = time.clock() #start the clock

#-----Generate the list of numbers to sort
while(iter < 100000):
    list.append(random.randint(0,10))  #modify this to change sorting speed
    iter = iter + 1
timetogenerate = time.clock() - start #current timer - last timer snapshot

#-----Sort the list of numbers
list = quickSort(list)
timetosort = time.clock() - timetogenerate #current timer - last timer snapshot

#-----Write the list of numbers
file = open("C:\output.txt", 'w')
for items in list:
    file.write(str(items))
    file.write("\n")
file.close()
timetowrite = time.clock() - timetosort #current timer - last timer snapshot

#-----Print info
print "time to start: " + str(start)
print "time to generate: " + str(timetogenerate)
print "time to sort: " + str(timetosort)
print "time to write: " + str(timetowrite)
totaltime = timetogenerate + timetosort + start
print "total time: " + str(totaltime)

--------修改了新代码------------------------- ---

def quickSort(array): #random pivot location quicksort. uses extra memory.
    smaller = []
    greater = []
    equal = []
    if len(array) <= 1:
        return array
    pivotVal = array[random.randint(0, len(array)-1)]
    array.remove(pivotVal)
    equal.append(pivotVal)
    for items in array:
        if items < pivotVal:
            smaller.append(items)
        elif items > pivotVal:
            greater.append(items)
        else:
            equal.append(items)
    return concat(quickSort(smaller), equal, quickSort(greater))

def concat(before, equal, after):
    new = []
    for items in before:
        new.append(items)
    for items in equal:
        new.append(items)
    for items in after:
        new.append(items)
    return new

【问题讨论】:

  • 在多次运行每个设置并对结果取平均值后,您是否会遇到这种情况?
  • 旁白:open("C:\output.txt", 'w')不应该是open("C:\\output.txt", 'w')吗?
  • @David 结果相当一致。这适用于范围 (0,10) (0,100) (0,10000)
  • 你的代码如何处理相等的元素?也许elem1 &lt; elem2elem1 == elem2 效果更好。
  • @Mikel 有趣的是,单个 \ 在代码中可以正常工作。我来自 Java 背景,所以转义序列对我来说仍然很新。

标签: python algorithm performance sorting quicksort


【解决方案1】:

我认为这与支点的选择有关。根据您的分区步骤的工作方式,如果您有很多重复值,当遇到许多重复时,您的算法可能会退化为二次行为。例如,假设您正在尝试对该流进行快速排序:

 [0 0 0 0 0 0 0 0 0 0 0 0 0]

如果您不小心执行分区步骤,这可能会很快退化。例如,假设您选择枢轴作为第一个 0,留下数组

 [0 0 0 0 0 0 0 0 0 0 0 0]

进行分区。您的算法可能会说较小的值是数组

 [0 0 0 0 0 0 0 0 0 0 0 0]

而较大的值是数组

 []

这种情况会导致快速排序退化为 O(n2),因为每个递归调用只是将输入的大小缩小一倍(即,通过拉出枢轴元素) .

我注意到在您的代码中,您的分区步骤确实这样做了:

for items in array:
    if items <= pivotVal:
        smaller.append(items)
    else:
        greater.append(items)

给定一个流是同一元素的一大堆副本,这会将所有这些副本放入一个数组中进行递归排序。

当然,这似乎是一个荒谬的案例——这与减少数组中的值数量有什么关系? - 但当您对许多不明显的元素进行排序时,它确实会出现。特别是,经过几次分区后,您可能会将所有相等的元素组合在一起,这会将您带入这种情况。

关于如何防止这种情况发生的讨论,by Bob Sedgewick and Jon Bentley 有一个非常棒的演讲,关于如何修改分区步骤以在存在重复元素时快速工作。它连接到 Dijkstra 的Dutch national flag problem,他们的解决方案非常聪明。

一个可行的选择是将输入分成三组 - 小于、等于和大于。一旦你以这种方式分解输入,你只需要对更少和更大的组进行排序;相等的组已经排序。上面的演讲链接显示了如何或多或少地就地执行此操作,但是由于您已经在使用非就地快速排序,因此修复应该很容易。这是我的尝试:

for items in array:
    if items < pivotVal:
        smaller.append(items)
    elif items == pivotVal:
        equal.append(items)
    else:
        greater.append(items)

顺便说一句,我从来没有写过一行 Python,所以这可能是完全非法的语法。但我希望这个想法很清楚! :-)

【讨论】:

  • 知道了。重复的元素使“较大”和“较小”列表的大小不成比例,这正是快速排序的性能开始下降的时候。
  • 您的 Python 基本正确,但正确的语法是 elif 而不是 else if
  • 我的代码已经修改,我确认了结果。对于 (0,10) 情况,110 秒降至 0.4 秒。
  • @advocate- 耶!我喜欢算法理论如何让你在现实世界中做一些很酷的事情!
  • @Matthieu M.- 实际上我以前见过那个;我不时地实现排序算法只是为了好玩。如果您想查看一些非常疯狂的排序,请查看 Smoothsort、introsort(大多数 STL 实现使用的)或笛卡尔树排序。其中第一个和最后一个在理论上非常漂亮,而中间的一个非常实用且易于理解。它经常与 Timsort 进行比较。
【解决方案2】:

我们知道的事情:

  1. 快速排序无序数组的时间复杂度为O(n*logn)
  2. 如果数组已经排序,则降级为O(n^2)
  3. 前两个语句不是离散的,即数组越接近排序,快速排序的时间复杂度越接近O(n^2),相反,当我们对其进行洗牌时,复杂度接近O(n*logn)

现在,让我们看看你的实验:

  • 在所有三种情况下,您都使用了相同数量的元素。所以,我们的n 你命名为x 总是100000。
  • 在您的第一个实验中,您使用了 0 到 100000 之间的数字,因此理想情况下,使用完美的随机数生成器,您会在相对无序列表中获得大部分不同的数字,从而符合 O(n*logn) 复杂度的情况。
  • 在第三个实验中,您在 100000 个元素的大列表中使用了 0 到 10 之间的数字。这意味着您的列表中有很多重复项,使其比第一个实验更接近排序列表。因此,在这种情况下,时间复杂度更接近 O(n^2)

同样足够大的n 你可以说n*logn &gt; n^2,你的实验实际上证实了这一点。

【讨论】:

  • 我同意其中的大部分内容,但如果可以的话,我想稍微不同意。数据是随机生成的,因此不靠近任何类型的排序结构。确实,对于 (0,10) 情况,范围要小得多。创建第三个列表“等于”,快速排序不需要递归排序,解决了我的问题。感谢您的时间和回复。
  • 这种对排序数组的快速排序降级到 O(N^2) 的误解是错误的。只有非常幼稚的快速排序才会如此,它总是选择第一个或最后一个元素作为枢轴。
【解决方案3】:

快速排序算法有一个已知的弱点——当数据大部分被排序时它会变慢。当您在 0 到 10 之间有 100000 个时,它们将比 0 到 100000 范围内的 100000 个数字更接近“大部分排序”。

【讨论】:

    最近更新 更多