Python 被O卡住了(n*logn)

Python 被O卡住了(n*logn),python,math,big-o,quicksort,Python,Math,Big O,Quicksort,我有一个快速分类: def quickSort(array): if len(array) <= 1: return array else: center = array[0] l_arr = [n for n in array[1:] if n <= center] r_arr = [n for n in array[1:] if n > center] return quickS

我有一个快速分类:

def quickSort(array):
    if len(array) <= 1:
        return array
    else:
        center = array[0]
        l_arr = [n for n in array[1:] if n <= center]
        r_arr = [n for n in array[1:] if n > center]
        return quickSort(left) + [center] + quickSort(right)
def快速排序(数组):

如果len(array)您似乎误解了算法的大O值实际上是什么,以及应该如何解释它。这里有三个问题需要解决:

  • 一个算法的大O阶并不能给你一个精确的步骤数,它是随着输入数量的增加将算法分成行为类
  • 在快速排序的情况下,重要的不是递归调用,而是比较
  • 您的输入不是一个平均情况,它实际上比平均步骤数要多
  • 首先,当人们说快速排序是一种O(N logN)算法时,他们的意思是,随着输入数量的增加,相对于输入数量,该算法将花费一定的时间。这称为算法的渐近行为。如果要生成越来越长的随机输入序列,并且测量了对每个序列进行排序所需的时间,您会注意到这些时间将按照N对数(N)趋势增长,这条直线只稍微偏离直线(但如果在图形的时间轴上使用对数标度,则可以使其变直)

    但这并不意味着对于任何给定的输入,该算法将精确地执行N次log(N)步。大O表示法允许您对算法进行分类,它们给出了算法的顺序,这可以看作是算法平均情况的上界,然后允许您将该算法与其他算法进行比较,从而实现相同的目标(这里是排序输出)并且知道当你给它大量的输入时,哪一个会表现得更好。当涉及到算法时,你关心的是知道你所要做的工作是否能在合理的时间内完成,而大O顺序告诉你什么算法能实现这一点

    这就是为什么,当涉及到渐近数学时,我们去掉任何常数或低阶分量,因为当给定接近无穷多个输入时,如果你有一个算法需要几乎2倍的无限时间,或者只有接近无限时间的一半,这并不重要。这两种方法都会生成直截了当的图形,在输入量相同的情况下,这些图形达到了不再值得等待的状态。当你谈到每24小时处理数十亿个输入时,不管是50亿还是60亿个输入,重要的是如果你用一个O(N^2)来替换算法,那么同样的工作将需要几十万年,但是如果你能找到一个O(N)你可以把时间缩短到45分钟

    算法通常也有最好和最坏的情况;一些排序算法在输入已排序时达到最佳情况,因此只需线性时间即可在N个步骤中生成相同的已排序输出。相反,如果输入的顺序是相反的,那么一个算法可能需要进行多达N次的N次比较,因此N次平方,这将是最坏的情况。然而,这并不意味着对于所有输入,它的行为都会像最好或最坏的情况一样

    对于Quicksort来说,最好的情况仍然需要O(n log n),顺便说一下,因为至多,每个分区步骤的枢轴值正好在中间。快速排序最糟糕的情况是它总是选择数组中的最低值或最高值作为轴心,因为这样您还没有对任何内容进行实际排序,并且您的递归实现必须对每个后续完整分区进行N次递归调用,每个分区每一步只包含一个元素

    如前所述,我们使用大O表示法对算法进行分类和比较。使用O(N logN)算法通常比使用O(N^2)算法更好,因为随着输入规模的扩大,O(N logN)算法完成任务的速度会(快得多)。我说的通常更好,因为如果你的输入总是很小,那么不同的因素会起作用,而“慢”O(N^2)方法实际上可能优于O(N logN)方法,因为它具有更低的固定成本,即完成单个步骤所需的工作量

    因此,您的特定输入可能是典型的案例。它不必遵循理想的N倍对数(N)步数,如果它倾向于最坏的情况,它可能需要更多的步骤

    对于快速排序,比较才是最重要的,因为随着N的增长,这是您需要做的更多的事情。仅仅围绕一个轴对输入进行分区需要N-1个步骤,因为您需要首先选择您的轴,然后将所有N-1元素与所选的轴值进行比较。合并结果(将第一个分区加上pivot再加上第二个分区的递归调用连接起来)也会有代价,因为您要创建一个包含N个输入元素的新列表。这也需要N个步骤,但因为这与分区的步骤数基本相同,所以我们可以忽略这一点,将其作为每N个输入的恒定成本。类似地,您的quicksort实现会对所有pivot进行两次比较,每个分区一次

    请注意,我们这里不计算递归调用。您使用了递归实现,但也可以使用堆栈和迭代实现快速排序(有效地用自己的堆栈替换Python为您管理的调用堆栈),并且仍然可以进行相同数量的比较。因此,与计算递归调用不同,对
    quickSort()
    的每次调用实际上应该被视为执行
    len(array)
    步骤来创建分区

    对于您的输入,让我们计算创建分区所需的比较:

    • [1,5,7,9,10,11,1,5]
      ,以1为轴心。将其他7个值与之比较,以创建
      [1]
      [5,7,9,10,11,5]
      分区:8个比较。
      • [1] [1, 5, 7, 9, 10, 11, 1, 5] is given, we take the num at 0 which is 1, 
         from then now we compare all items in list as per the code, found 1 <= 1. 
         We put it in the left array. We put the rest in the right array. 
         [1] + [1] + [7, 9, 10, 11, 5]. We sort r_arr recursively which give us
         [5] +[5] + [7] + [9, 10, 11]. We sort r_arr again. [] + [9] + [10, 11]. And 
         again. [10] + [11].
         [1, 5, 7, 9, 10, 11, 1, 5] n elements
         [1] [5, 7, 9, 10, 11, 1, 5]n - 1
         [1][1][5, 7, 9, 10, 11, 5] n - 2
         [1][1][5][7, 9, 10, 11, 5] n - 3
         [1][1][5][5][7, 9, 10, 11] n - 4
         [1][1][5][5][7][9, 10, 11] n - 5 
         [1][1][5][5][7][9][10, 11] n - 6
         [1][1][5][5][7][9][10][1]  n - 7